[Copyright info: this file is in the public domain.] This is a self-study course, designed for those who want to learn C. It will not turn you into an expert programmer, not even an expert C coder. Nothing can do that but lots and lots of practice. (In general, you're not an expert at something until you've done it for roughly ten thousand hours - the exact time cited varies depending on the particular skill in question and on whom you ask, but is generally somewhere in the neighbourhood of ten thousand hours. If you do something for a nominal full-time work week, forty hours a week, for five years - that's about ten thousand hours. And, of course, like any major skill, learning to program, or even learning to program in C, is not a place at which you can arrive, but a road leading as far over the horizon as you are willing to follow it.) What this course *does* do, or at least tries to do, is to turn you into a C coder good enough to write some simple programs and, if it's what you want, embark on that ten thousand hours of practice. If you already know some other programming language, great. You'll find that some of the content seems ridiculously obvious to you. Exactly which parts those are will depend on what other language(s) you know and how your mind works. If you don't yet know any other programming language, I suggest you set this course aside and go learn a few other languages first. C is not a particularly good first language. It's a training-wheels-off language; making it your first programming language is a little bit like trying to learn to drive in a Formula 1 racer: it can be done, but the learning curve will be steep and probably a lot less fun than if you picked something better suited to the task. There are a _lot_ of languages out there, and I'm not really competent to tell which ones are good first languages. But someone whose opinions on technical matters I greatly respect suggests Python, and at this writing (2021) the little I know of Python makes me think that's a reasonable suggestion. The rest of this course will, of course, assume you have decided to proceed. I will try to avoid assuming you know any other languages. Even if you took my advice above and aren't reading this until you know a few other languages, which parts you can skip will, as I also said above, depend on which those other languages are and on how your mind works. So I will try to avoid assuming any of it. To follow along with this course, you will need some things. The most obvious, of course, are a copy of the course itself (which you presumably have, or you wouldn't be reading this) and the time and inclination to embark on it. But, to really get anything out of the course, you will also need access to a computer to try what you've learned out on. The kind of learning you can get just from reading is valuable, but nothing short of actually doing it will bring you even a basic level of proficiency. You don't need your own computer (though of course that is in many respects the best way to do it); if someone you know is willing to give you suitable access to their computer that should be enough. These days, C has become ubiquitous enough that most operating systems support it in some form. Which OS you're using will matter in many practical respects, but for most of the purposes of this course, it doesn't. C proper, the language, is the same across all of them, except for a comparatively few things the language specifically does not specify, or specifies in only some respects. Differences between OSes matter most when your program starts trying to interact with the world around it. Of course, in order to be useful, most programs will have to do this in some form, but this course will mostly keep its interaction to the barest minimum. I do say some OS-dependent things in a few cases where they seem most necessary; they will always be marked as such. If you're planning on setting up a machine to do this course on and haven't already decided on an operating system, I would strongly recommend one of the open-source Unix variants. Linux, in its multitude of forms, is the most popular at the time of writing, but there are many others, and, for the purposes of this course, the differences between them are ignorable. Second choice would be a closed-source Unix variant, provided it has, or can be set up with, a C development environment. Sprinkled through the text are exercises. I urge you to at least attempt each of them. This is not the kind of course where you get a grade and can flunk out; the point is to learn, and there's a lot you just won't learn if you don't actually try them. And, as I said, you don't have to submit your results anywhere and get graded; there is no cost to trying the exercises beyond the time they take. ---------------- Here's a rudimentary first C program. It may seem to be getting ahead of myself to be giving a functioning program so early. But you'll need *something* for the first exercise. Don't worry about what the various pieces of this mean just yet. #include int main(void) { printf("Hello, world!\n"); return(0); } You will need to get this into your computer somehow. This could mean typing it into a text editor. It could mean cutting and pasting from the course text. It might even mean something else, though I have trouble thinking what. (Some examples below have line numbers. If you're cutting-and-pasting, you'll need to edit the numbers out after pasting.) You will also need to figure out how to compile it into something you can run. Exactly how to do this is one of the OS-dependent parts. On a Linux system, for example, if you put the above text in a file called hello.c (the convention on most Unix variants, such as Linux, is to use names ending in ".c" for the human-written form of C programs), you might then type "cc -o hello hello.c" to convert that text (the so-called source code) into a form the computer can run directly. The term used for the latter form varies more from system to system; this course will typically call it the binary, the executable, or the machine code. How you then tell the computer to actually *run* your program also varies; if you compiled it as above, you might then type something like "./hello" to run it. -> Exercise: Get - or get access to - a suitable computer. Try getting the above program into the computer. Try compiling it and running it. Once you have it working, when you run it it should tell you Hello, world! If this doesn't work at first, don't despair. I can't cover all the possible ways it might go wrong here, especially since I am familiar with only a tiny fraction of the possible OSes you might be using. I can only suggest asking someone familiar with that OS and/or that development environment. Once you have it working, congratulations! This is possibly the biggest single hurdle, if only because it's probably the one I am least able to help you with - details matter, the variety of systems is huge, and the ways things can go wrong are legion. At many places in this course, I may say something that is not strictly true. I do this in order to avoid hitting you with too much complexity for that point in the course. I will say so, though, when I do, so you can know that what I've just told you is a simplification, designed for the perspective you have at the time; the full truth will come along in due course. Doing the above - printing out "Hello, world!" - is a traditional first program when learning pretty much *any* new programming language: print out a short, simple piece of text. This is because getting it working involves clearing most of the boring (but essential) hurdles such as creating the program, converting it (if necessary) into a form the computer can run directly, and running it. Once those are cleared, you can move on to actually starting to do something interesting. So, now that you've managed to print that out, where do we go from here? Let's start picking that program apart. We can't really go into the full details of all of it this soon, but there is at least one important concept it illustrates: the concept of a statement. C programs are mostly made up of statements. It's a bit of a simplification, but you can think of a statement as a small "thing to do", a building block out of which a program is assembled. In the case of the above program, reproduced here with line numbers added, 1 #include 2 3 int main(void) 4 { 5 printf("Hello, world!\n"); 6 return(0); 7 } there are two statements: line 5 is one of them and line 6 is the other. (For the moment, just think of the other five lines as necessary framework: the statements that do useful things need to be framed in order to work, just as a picture needs to be framed in order to be hung on the wall.) The statement on line 5 does the "real work": it prints the text. We'll get into the details, such as how this happens, later. The statement on line 6 terminates the program cleanly. Again, we'll get into the details later. Think of a statement as "something to do" followed by a semicolon. (There are actually many kinds of statements, some of which do not fit that description, but it's close enough for the moment.) The semicolon serves as a terminator, indicating that the statement is over. This probably appears unnecessary at this point. In a sense, it is; there are languages that terminate statements (or analogs thereof) in other ways. But that's the way C chose; we'll see, later, why it's important to have that terminator there. Of course, with just those two kinds of statements - print something and terminate the program - you can't do very much. Most tasks call for doing some computation as well. This leads into the next important concept: the expression. An expression represents computation of some sort. For example, if we want to find out what 1234 plus 5678 is, the expression 1234 + 5678 will compute that value. Of course, computing values is of little use if you can't do anything with them. Earlier, I introduced printf(), but all we had it do was print a fixed string. It can do much more than that. Of immediate interest, it can also print out numbers. If we write printf("Sum is %d\n", 1234 + 5678); then, when we run it, we get Sum is 6912 (Again, don't worry about the details of why that does what it does; printf is a relatively complex beast and it will take a while before we're ready to explore all of it. For the moment, just treat the %d as the way printf says "plug a computed value in here".) Expressions can do a lot more than just add numbers, of course. Like most programming languages, C builds up its expressions from so-called `operands' and `operators', where an `operand' is something like 1234 above - a piece of data that's operated on - and an `operator' is something like + above, something that does the operating. C has a moderately large set of operators. It would be premature to give the whole list now; we'll introduce them gradually as we have use for them, mentioning for the moment only the four major arithmetic functions + - * / and parentheses ( ) for grouping. As is usual (in both mathematics and most other programming languages), these operators have what is called `precedence', which controls how ambiguous cases, such as 1+2*3, are interpreted. (The ambiguity here is: does this mean add 1 and 2, then multiply that by 3? Or does it mean multiply 2 and 3, then add 1 to that?) * and / are said to have higher precedence than + and -, which means that they happen earlier: given an expression such as 1+2*3, the * happens before the +, yielding, in turn, 1+6, then 7. If you want the + to happen first, use parens, as in (1+2)*3. C's operator precedence hierarchy has some aspects that make sense (such as * and / having higher precedence than + and -) and some that don't (involving operators we haven't seen yet). C programmers disagree on whether it's better to learn the precedence hierarchy and take full advantage of it or to forget it and parenthesize everything. (Personally, I tend towards the latter camp, to the extent that I will even write 1+(2*3) rather than 1+2*3.) -> Exercise: Try changing the hello-world program to print out various arithmetic expressions. Experiment in various directions. Try to find some which appear to not work right, that is, which print out something other than what you expect. Okay, so we can do rudimentary computation. This is, again, of limited value by itself: we'd like to be able to save computed values somewhere so we can use them again later. This leads us to what are called variables. A variable is, basically, a place to store a value. (It's called a `variable' because it is able to vary, that is, it doesn't have to always have the same value.) In C, as in most languages, variables have names. In C, these names are just strings of letters, digits, and underscores, but they cannot begin with a digit. (In some cases, depending on the system and the compiler, other characters may be permitted; we'll stick to just these.) For example, we could call a variable `a' or `length' or `Complicated_Value_Number_7', but not `99balloons' or `long-hair' or `done?'. For most of the purposes of this course, we will pretend that variable names also cannot begin with underscores; they actually can, but there are portability reasons, which I'll discuss later, to avoid such names. There is also a list of some 30 or 40 names which look like variable names but are actually built-in pieces of the language, so-called `keywords'. You can't, for example, call a variable `void'; that's a language keyword. But C considers upper-case and lower-case distinct; `length' and `Length' and `lEnGtH' are, as far as C is concerned, three completely different (potential) variable names, as different as `length' and `chosen' and `not_42'. The usual term for this kind of name is `identifier', which I'll use at some points below and which you may see elsewhere. Just so you don't trip over any of them - or, rather, so you can recognize it when you do, because eventually you will - here's the list of C keywords, as of the 1999 revision of the language ("C99"): auto enum restrict unsigned break extern return void case float short volatile char for signed while const goto sizeof _Bool continue if static _Complex default inline struct _Imaginary do int switch double long typedef else register union We'll be at least touching briefly on most of these. Depending on exactly which revision of the language the compiler you're using is for, the list of identifiers it recognizes as keywords may change slightly, but it's unlikely to differ from the above list very much. (Two common additions to the list, for example, are fortran and asm.) There is another important concept lurking here, the concept of the data type, but that is something we'll mostly defer for a bit. ---------------- So, how do we use variables? We need to introduce two new things for this: declarations and assignments. Declarations are how you tell the computer that a particular identifier names a variable. For example, if we want `length' to be a variable, we might write int length; The `int' part is, as you can see from the list above, one of the language keywords I mentioned; it's related to the data type concept I'm deferring talking about for the moment. For the moment, just consider it part of how you say that `length' is supposed to be a variable. You can declare multiple variables in a single declaration, by naming their identifiers with commas in between, as in int cats, dogs, snakes; (This is another of those simplifications; it's not quite as simple as just naming identifiers with commas in between. But the differences are relevant only in cases we haven't covered yet.) Whether it's better to use a single declaration or multiple declarations is another matter for disagreement. I'll touch briefly on this again, below, where I talk about whitespace. Historically, declarations have to come "at the beginning", which here means just after the { that's part of our boilerplate frame. (This is another simplification; there are some other places they can appear, mostly places we haven't seen yet.) C, like most languages, is evolving, and recent versions relax this somewhat. For the moment, we'll ignore that; even in recent versions, a variable's declaration has to appear before you can use it, and putting the declarations right at the beginning ensures that. It is important to note that a declaration does not cause a variable to have any particular value. (As described here. This is a simplification; it is possible for a declaration to cause a variable to start off with a well-defined value, under circumstances we'll describe later.) If you use a variable's value without storing a value in it, you'll get trash. If you're lucky, it'll be obvious trash; if not, it'll be subtly wrong. If you're really unlucky, it'll be just what you expect when you test and then trash when you put it into production. (The trash will not always be predictable.) Assignments are the way variables get values. (Again, a simplification; there are a few other ways variables can get values.) Assignments, at their simplest (they can get a bit more complicated, but we'll defer that), are just an = (the assignment operator) with the variable on the left and an expression, giving the value to assign, on the right, with the usual semicolon terminator. Here are three examples: temperature = 212; legs = birds * 2 + cats * 4 + ants * 6; total = first_part + second_part - overlap; As you can perhaps infer from the examples on the right-hand side above, getting the value out of a variable is done by just naming the variable where you want to use its value. (Again, a simplification; in some cases we haven't covered yet, it's not quite that simple.) To put it all together into an example: 1 #include 2 3 int main(void) 4 { 5 int cats, dogs, birds; 6 int pets; 7 8 cats = 2; 9 dogs = 4; 10 birds = 3; 11 pets = cats + dogs + birds; 12 printf("Pet count is %d\n",pets); 13 return(0); 14 } Once again, we have the same five framing lines: four at the beginning and one at the end. There are two lines of declarations, declaring four variables (cats, dogs, birds, and pets). Line 7 is not essential; it is there just to give some visual separation between the declarations and the code. (Line 2 is actually not essential either; I'll talk about this a little more soon.) Then we set cats, dogs, and birds by assigning simple values to them. Then we compute the sum of those three on line 11 and assign the result to pets. On line 12, we print the resulting value of pets. All that remains is for line 13 to close everything down cleanly and then we end the frame with line 14. ---------------- At this point, you may be thinking that variables improve things somewhat, but not very much. And, with just what I've described so far, that's correct. Let's introduce one more piece. 1 #include 2 3 int main(void) 4 { 5 int val; 6 7 scanf("%d",&val); 8 printf("%d squared is %d\n",val,val*val); 9 return(0); 10 } Just as printf prints output, scanf reads input. There are more concepts lurking here that we haven't got to yet. For the moment, just treat most of line 7 as magic; we'll get to it eventually. This example shows only a tiny fraction of what scanf can do; as used here, it reads a number and stores it in the variable - val, here - named. (This example also sneaks in one more thing: a single printf can print more than one number.) If you run this, it will appear to hang. It's waiting for input. If you type, say, 7 (you will likely have to type something after it, like RETURN or ENTER, depending on the system), it will tell you 7 squared is 49 But if you instead type 44, you'll get 44 squared is 1936 Finally, we can actually do a little computation: input, processing, and output. (The processing we can do is pretty minimal so far.) -> Exercise: Modify the program so it prints out some other computation. Maybe print out the input number cubed. Maybe accept two numbers and print out their product. Maybe something else; try to think of something meaningful to you. Now, as remarked above, the processing we know how to do so far is pretty minimal. We can do arithmetic, but that's about it: we can't do tests, we can't loop (that is, we can't repeat computation), we can't even factor out common computation (that is, we can't take repeated code and encapsulate it in any way). ---------------- It is time to introduce another concept many languages share, the concept of flow control. This is something that's been implicit in the programs we've looked at so far. It has not been explicitly stated that when we write, to cite the most recent example, scanf("%d",&val); printf("%d squared is %d\n",val,val*val); return(0); then things should be done in that order: the scanf, then the printf, then the return(0). This may seem so obvious it doesn't need stating, and, in this particular case, perhaps it is. But let's suppose we want to take a number from 0 to 100 and then print out that number multiplied by 100 minus the number. (This may seem contrived, and to an extent, it is - but such things do occur naturally. We might have, for example, 100 five-foot pieces of fencing and we want to fence off a a rectangle with a cliff and a road forming two sides. This then gives the area of the rectangle, in square fence-pieces, if the input number is the number of pieces we use for one side.) Given what we know so far, about as close as we can come to this is scanf("%d",&u); printf("%d\n",u*(100-u)); but, if the supplied number is less than 0 or greater than 100, this prints out a negative number. If that's what we want, of course, that's fine. But, if we want an out-of-range number to produce an error message instead of a negative result (as we might in the fence example, where a negative result makes no physical sense), we want something else. This is where flow control becomes important. To do that - to print the above for numbers 0 through 100, but an error for numbers outside that range - we might do 1 #include 2 3 int main(void) 4 { 5 int u; 6 7 scanf("%d",&u); 8 if ((u >= 0) && (u <= 100)) { 9 printf("%d\n",u*(100-u)); 10 } else { 11 printf("Input out of range!\n"); 12 } 13 } Here we have the same five framing lines as usual. We have a declaration, line 5, declaring u to be a variable (we'll use it to hold the input number). Line 7 reads the input number. Lines 9 and 11 should be understandable enough at this point. The new stuff is lines 8, 10, and 12. This example also introduces three new operators (>=, &&, and <=), two new keywords (if and else), and an additional use for { and }, characters that so far have appeared only as two of our framing lines. The new { and } characters here are performing grouping, collecting all the statements within them into a so-called `block'. In this case, the { on line 8 and the matching } on line 10 contain just one statement, the one on line 9; similarly, the { and } on lines 10 and 12 also contain just one statement, the one on line 11. The new expression (u >= 0) && (u <= 100) on line 8 is C's way of saying "if u is greater than or equal to zero, *and* u is less than or equal to 100": the >= and <= are the "greater than or equal to" and "less than or equal to" parts and the && is the "and". (This is another simplification, though a fairly mild one this time; we'll get into more detail below.) The if-{block}-else-{block} construct takes the expression after `if', typically called the `test' or the `control' expression, and uses it to control which of the two blocks gets executed. The other block, the one not getting executed, does nothing at all. So, for example, if you type 18 as the input number, then the test passes, so line 9 executes and line 11 doesn't; similarly, if you type 214 as the input number, then the test fails, so line 9 does nothing and line 11 runs. (There is another simplification here. The braces around a block are not actually required when the block consists of exactly one statement. I recommend always using the braces until you have a firmer grasp of precisely what constitutes a statement, and possibly even then.) The `else' and the second block are optional; if they are omitted, nothing happens in the `else' case, as if you had written `else { }'. (You can, if you like, write that - an explicit empty else block - but there's usually no reason to do so.) Thus, while in general things are done in the order they're written in the program, constructs like this allow skipping some things. There are, of course, other comparison and logic operators. Some of the most useful ones, in addition to the three mentioned above, are == "equal to" != "not equal to" < "less than (but not equal to)" > "greater than (but not equal to)" || "or" (If you use && and || in the same expression, you should probably use parens to make sure it's interpreted the way you mean. As it happens, && has higher precedence than ||, so that, for example, X && Y || Z means (X && Y) || Z, not X && (Y || Z), but this is so easy to forget that at least one major C compiler actually has a warning you can turn on that warns about such unparenthesized mixtures. Personally, I think it would have been better to have had no defined precedence between them, requiring parens for any mixing.) This is also a good time to introduce another subtlety. There are actually two kinds of operators. (Simplification alert: there are really three kinds, in C; we'll get to the third kind later. There are also some other things which can be thought of as operators but which don't fit this pattern; I'll get to them eventually.) One kind takes two arguments; all the operators described above are this kind. Depending on the author, such operators are called `binary', `dyadic', or such terms. The other kind takes only one argument. Such operators are called `unary', `monadic', or suchlike. I usually use `binary' and `unary'. (The third kind of operator I mentioned above is a ternary, or triadic, operator. Some languages have operators with even other arities, such as zero-operand operators (typically called `nilary' or `niladic') or those with more than three operands. C doesn't have any such in the usual sense, though if you want you could think of any constant as a niladic operator returning that constant, or function calls - which we haven't yet discussed - as multi-operand operators.) In C, binary operators generally between their operands; that is, they look like operand-1 operator operand-2 (This convention is typically called `infix'. Some other languages don't do this. Some write operator operand-1 operand-2 (`prefix') - Lisp is perhaps the most famous example - and some write operand-1 operand-2 operator (`postfix'), with FORTH and PostScript as relatively well-known examples. But we're dealing with C here, so we'll be using infix syntax most of the time; I'll point out any exceptions.) Unary operators sometimes appear before their operands, sometimes after, depending on the operator. We haven't met any of them yet, but there are two I want to introduce now: - and !. These are prefix operators, that is, they appear before their operands. (In case you're curious, the analogous word for those that appear after their operands is `postfix'.) Unary - negates numbers, turning, for example, 4 into -4 and -10 into 10. You can use it as in j = - (i * 4); Unary ! complements the outcome of a test, so that, for example, if we write if (! (x < y)) then the effective test will be the reverse of what it would be if we had written if (x < y) The extra parentheses in the first example are necessary; if you write if (! x < y) this will be taken as if ((! x) < y) which is unlikely to be what you mean. (Unfortunately, largely for historical reasons, it usually will not be an error. This will make more sense when we go into details on exactly how comparison results are handled in C.) Note that the outer level of parentheses are always required; they are part of the syntax of if, not, strictly, part of the expression. Omitting them, as in if x < y is an error. (This one will generally be caught by the compiler. It also is C-specific; there are languages in which "if x < y" is fine.) -> Exercise: Read a number of hours and print out the number of minutes in that many hours (there are 60 minutes in each hour) - but if the number of hours is negative (less than zero), print an error message instead. ---------------- As useful as if and if-else are, they are limiting. They still don't allow looping (repeating the same code over and over). Like almost all programming languages, C has looping constructs. The simplest is probably `while'. A while loop looks like while (test) { statements } In passing, this might be a good place to note that C is almost entirely free-form when it comes to code layout; there are only a few places where a line break is semantically different from a blank space, or where having blank space differs from not having any. (They mostly involve the preprocessor, something we haven't discussed yet. The only trace of it we've seen so far is the #include line in the examples.) Also, you can introduce whitespace almost anywhere where it doesn't break up an identifier or a number, and delete it almost anywhere where it doesn't cause two identifiers or numbers to merge, or the like. Oh, or inside " " (or ' ', which we'll get to later). For example, the example above #include int main(void) { int val; scanf("%d",&val); printf("%d squared is %d\n",val,val*val); return(0); } could instead have been written #include int main ( void){int val ;scanf ( "%d" , & val);printf( "%d squared is %d\n" , val,val * val );return ( 0);} or #include int main(void){int val;scanf("%d",&val);printf( "%d squared is %d\n",val,val*val);return(0);} But if you delete whitespace so as to merge two language elements ("tokens") intmain(void) or intval; or introduce it so as to split one up retu rn(0) then you've just changed the meaning (and usually introduced a syntax error). Except for a very few special-case contexts such as the International Obfuscated C Code Contest, it is best to lay code out in a way that reflects the conceptual structure of the code. Exactly what this means depends, of course, on whom you ask; the examples so far have been laid out in one popular style, but there are plenty of competing styles. (At some point I'll give an example or two.) If you're working with other people, it's usually best to agree with them on a single style; if you're working on your own, pick a style that makes sense to you, and/or one that works well with the code-writing tools you're using. In either case, the end goal is generally readability; I have yet to see a style rule that I don't think deserves to be broken on at least a few occasions. ``A foolish consistency is the hobgoblin of little minds''; slavish adherence to rules is perhaps better than completely wonky layout like the above two extreme examples, but it's better to remember that readbility is more important than rule conformance. (In most contexts. There are some cases, such as working in industry with management-imposed style edicts, where conformance is more important. Even there, though, I personally think elevating guidelines to the status of edicts is a mistake.) Readability is also the main reason to choose whether to use one declaration or multiple declarations to declare multiple variables. The consensus - to the extent that consensus exists - seems to me to be that it is best to use separate declarations unless the variables are related somehow. For example, int length, width, height; but int post_type; int temperature; int piece_count; but there are disagreements in both directions on that. The only case where you cannot use a single declaration is when the types differ, something we haven't seen yet. (Simplification: in some circumstances, involving language features we haven't seen yet, you can declare variables of different types in a single declaration. Even then, though, the types have to be related in certain ways we haven't yet seen.) I'll talk about multiple variables in a single declaration more later. In the meantime, I recommend you stick to a single declaration per variable; there are fewer unpleasant surprises that way. Okay, back to while loops. while (test) { statements } (As in if statements, the { } are actually optional if there is exactly one statement in the loop body. As mentioned above, I recommend using { } until you have a firmer grasp of the precise details of what constitutes a statement.) When control flow reaches this, it will first evaluate the test expression. If the test passes, it will then execute the statements, the so-called body of the loop, then go back to re-evaluate the test expression. This continues, repeating until the test fails, at which point execution picks up with the next statement after the loop. As a simple example, suppose we want to read a number and then count down from that number to zero, printing each number on the way. We have all the tools to do this, though there is one subtlety which deserves mention. Here's the code. 1 #include 2 3 int main(void) 4 { 5 int i; 6 7 scanf("%d",&i); 8 while (i >= 0) { 9 printf("%d\n",i); 10 i = i - 1; 11 } 12 return(0); 13 } Lines 1-7, 9, and 12-13 should be familiar by now. Lines 8 and 11 are the while loop construct. Line 10 is where the subtlety lurks. The subtlety is that this is the first time we've had the same variable appear both on the left-hand side of an assignment and as (part of) the right-hand side. Once you realize that the value to be assigned must be computed before the assignment happens (you can't really store a value you don't yet have into a variable), this does the logically consistent thing: it computes the right-hand side value, _then_ stores it into the variable on the left-hand side. So line 10 amounts to decreasing the value stored in i by one. To a mathematician, used to using that notation to mean "these two things are equal", that looks like nonsense - but the = operator in C is not a mathematician's description of two things being equal; instead, it's an action, an indication that a value is to be stored somewhere. It might have been less confusing if this operator were spelled something like <- instead of =, but it's much too late to fix that now. (Some other languages do try to address this. Pascal, for example, spells its version of this operator := rather than =, and APL uses a left-arrow for its version.) So, if we type 2 as input, then what happens is Line What happens ---- ------------ 7 reads input and sets i to 2 8 test: i >= 0 i is 2, so the test passes. The body is executed: 9 prints 2 10 sets i to 2-1, or 1. This is the end of the body, so it goes back to the test. 8 test: i >= 0 i is 1, so the test passes. The body is executed: 9 prints 1 10 sets i to 1-1, or 0. This is the end of the body, so it goes back to the test. 8 test: i >= 0 i is 0, so the test passes. The body is executed: 9 prints 0 10 sets i to 0-1, or -1. This is the end of the body, so it goes back to the test. 8 test: i >= 0 i is -1, so the test fails. Execution picks up after the loop, which here means.... 12 return(0); Shuts down execution cleanly, as usual. Thus, we'll see 2 1 0 If we type 5, we'll see 5 4 3 2 1 0 It is important to note that while loops are what is called `top-tested': the test is performed before the body is executed even the first time. Thus, for example, if we enter -6, we'll get nothing at all, because the test fails the very first time through. There is a variant which is bottom-tested, with the body always executed once before the test is checked. We'll get to that one later; it gets used significantly less than the top-tested form. We finally have enough pieces to do something that begins to be useful. Let's write something that reads numbers as input, keeping a running total, until 0 is entered, at which point it prints out the total and shuts down. -> Exercise: Try to write this program before reading the description below. You may or may not succeed; whether or not you succeed is not nearly as important as what you learn in the process. There is no single right way to do this. I'll describe one possible way of constructing this one in some detail; please remember that this is just one of many possible ways to put this program together. At this stage, anything that works is good enough. Start with our usual framing lines: #include int main(void) { ... } We'll need at least two variables, one to hold the input number and one to hold the running total. We may need more; let's declare just these two for now and add any others later, as we discover we need them. We'll also need a while loop, with some as-yet-unknown test and body. We might need code before and/or after the loop, too. #include int main(void) { int input; int total; ... while (...) { ... } ... } What do we write next? Let's start with the body of the loop. What needs doing over and over? We need to read the input number, and, if appropriate, accumulate it into the running sum. We also need to get out of the loop when the input is zero. Let's add a variable indicating whether we're done looping or not. int done; And then let's write the loop body: while (...) { scanf("%d",&input); if (input == 0) { done = 1; } else { total = total + input; } } Now, we can write the loop condition: while (done != 1) { But we also have to make sure done isn't 1 when we start, or the loop may not run at all (recall that done will start out holding trash; that trash might happen to be 1): done = 0; Then, once we're out of the loop, we need to print the total and shut down: printf("%d\n",total); return(0); Putting it all together so far, 1 #include 2 3 int main(void) 4 { 5 int input; 6 int total; 7 int done; 8 9 done = 0; 10 while (done != 1) { 11 scanf("%d",&input); 12 if (input == 0) { 13 done = 1; 14 } else { 15 total = total + input; 16 } 17 } 18 printf("%d\n",total); 19 return(0); 20 } This seems like a good time to mention another aspect of programming in C, one often skipped by books on the language: debugging. Like programming, debugging is a skill; fortunately, it is a skill closely related to the skill of programming. (Just as `programming' is not the same as `programming in C', `debugging' is not the same as `debugging C'. But, just as programming qua programming cannot be taught, only learnt - to quote the Loginataka, "All those competent to teach the Way know that it cannot be taught, only pursued with joyous labor" - so too it is with debugging. Programming in C can, perhaps, be taught, though, or this course would be pointless.) -> Exercise: I have (deliberately) skipped something in the above program. This exercise is to find and fix it on your own before reading on. (Maybe you've already noticed it. If so, congratulations!) As above, whether you succeed is not nearly as important as what you can learn from trying. When I tried this, I entered 1, 2, 3, and then 0, and should have gotten 6. I didn't. I entered 1 2 3 0 and I got -1145425107 (You will likely get a different number. You might even get the 6 you might expect.) What went wrong? Let's trace through it: 9: done gets set to 0 10: test: done != 1 test passes, so we execute the body. 11: scanf: input gets set to 1 12: test: input == 0 test fails, so we do the else-part 15: total = total + input so we take the values in total and input... ...wait, what's the value in total? That's right, we never gave it one! Recall what I said, above, about a declaration not giving a variable a value? total starts off with some value, but it will be meaningless garbage; unless it happens by chance to be zero, we'll get incorrect sums printed. In my example, total must have started off holding -1145425113. Let's add a line: 1 #include 2 3 int main(void) 4 { 5 int input; 6 int total; 7 int done; 8 9 done = 0; >>> total = 0; 10 while (done != 1) { 11 scanf("%d",&input); 12 if (input == 0) { 13 done = 1; 14 } else { 15 total = total + input; 16 } 17 } 18 printf("%d\n",total); 19 return(0); 20 } And now it works. Some compilers have options that can, sometimes, catch uninitialized variables like this. If yours has one, it's sometimes useful to use it. Don't count on it, though; all such compilers I know of make mistakes in both directions, both occasionally complaining about perfectly good code and occasionally missing real problems. ---------------- Now, it's time to partially de-simplify one of the simplifications above. You may recall I mentioned data types. It's time to learn a little bit about them. A data type is a term for a kind of data. Most languages, including C, classify values into various so-called types. `int', which we've been using right along, is an example. So far, it's the only example we've seen. (The only example we've seen explicitly, that is. There have been values of other types running around, but they've all been under the hood, not things we've dragged out into the light of day.) The name, "int", is an abbreviation of "integer", and that's what they are: a machine approximation to mathematicial integers. Whole numbers, that is, like three, or four, or seventeen, or minus ten. Their distinguishing characteristic is that they have no fractional part. Three and a half, for example, is not an integer; the "and a half" is a fractional part, which integers lack. Unlike mathematicians' integers, however, C ints are limited in range. C promises that they can handle a range of -32767 to 32767; in practice, these days, you can usually expect them to handle -2147483648 to 2147483647. If you exceed the limits, whatever they are on your system, C does not promise what happens. In practice, what happens is usually that the ideal result has a number (the "modulus", I'll call it) added to it or subtracted from it as often as necessary to bring the result back into range: to put it the way hardware designers typically would, the overflow is ignored. A mathematician would say that the number is reduced modulo the modulus (which is why I'm calling it a modulus). For example, on C implementations where the limits for ints are -2147483648 to 2147483647, they typically operate as outlined above, with a modulus of 4294967296 (2 multiplied by itself over and over until a total of 32 twos have been multiplied together, what a mathematician would call the 32nd power of 2). Thus, if you add 2000111000 to 2000111000, what happens? Well, the mathematically correct result is 4000222000. On such a machine, C does not promise what happens, but, typically, what will happen is that you'll get, instead, -294745296: 4000222000 minus 4294967296. If you multiply 12345678 by 87654321, the mathematically correct result is 1082152022374638. But what you'd get on such a system is 652409070: 1082152022374638 minus 251958 times 4294967296. (If you subtract off some other multiple of 4294967296 instead, you'll find the result isn't in the range -2147483648 to 2147483647.) That description - ints range from -2147483648 to 2147483647, and overflow reduces modulo 4294967296 - matches practically all C implementations you're likely to run into at this writing (2021). But it's worth keeping in mind that the result is not defined by the language; as far as C is concerned, as soon as you multiply 12345678 by 87654321 using ints on any system where the maximum int value is less than 1082152022374638, your program could print "Your mother was an elderberry and your father smelt of hamsters!" over and over, even if your source code doesn't appear to call for that. (Yes, I am aware that's not the original quote. I'm mangling it further for the sake of silliness.) No compiler would do *that*, though, possibly except to make a point. What _is_ comparatively likely is that the compiler will assume your code doesn't overflow and generate code based on that. For example, if you write int n, m; int p; ...code to set n and m... if ((n < 0) || (m < 0)) { printf("n or m is negative\n"); } else { p = n * m; if (p < 0) { printf("Overflow!\n"); } else { ...do something else with p... } } then the compiler would be justified, from a C perspective, in optimizing away the p < 0 test and the "Overflow!" printout. This is because the only way multiplying two non-negative ints (and at that point, n and m are known to be non-negative) can produce a negative int is overflow, and, as soon as overflow happens, the program's behaviour is no longer constrained by the C specification. At all. So omitting that printf is entirely permissible in the case of overflow, and, in the absence of overflow, it can't be reached anyway, so it can be removed entirely. Or so goes the reasoning. In my opinion, any compiler that goes that far is past the bounds of reasonable and into the realm of being gratuitously obnoxious for the sake of making a point. I recommend not using compilers that insist on behaving that way; I mention the issue here largely because, if you use C a lot, sooner or later you'll run into something of the sort, and you need to be able to understand it when you do. (There are many other corners of the language where behaviour is similarly undefined; overflow of int values is far from the only such.) With that digression is out of the way, it's time to get back to talking about data types. The next data type I want to talk about (largely because it will figure prominently soon) is what's called `floating point'. Strictly speaking, floating point is not a data type in the C sense; it is a family of related data types. But at this point I'm speaking in generalities. Just as machine ints are an approximation to a mathematician's integers, floating point numbers are an approximation to a mathematician's real numbers. Loosely speaking, they are numbers that (potentially) include a fractional part: three, eighteen, minus seven and a half, three ten-thousandths, or the like. Very large or very small numbers can be written with a form of exponential notation (sometimes also called "scientific notation"). A floating-point constant consists of a number, possibly containing a decimal point, possibly followed by a power of ten - but it must contain at least one of those two, either a decimal point or an power-of-ten clause, or it will be taken as an integer constant instead. (In some cases, you can use an integer constant as if it were a floating-point constant, but there are also cases where the difference matters.) The exponential notation consists of the letter e (or E) followed by an integer (which is taken to indicate a power of ten) appearing after the rest of the number. (For those of you with enough mathematical training for the statement to mean something, the major ways that computer floating point numbers differ from mathematical reals are (1) limited range and (2) limited precision.) For example, 3.14157777 is a floating-point constant. So are .00001, 6.28e21, 2.0, and 4e-40. -2.0 technically is not a floating-point constant; it is the unary - operator applied to the floating-point constant 2.0. But in practice this is a distinction without a difference; you can think of -2.0 as a floating-point constant for practical purposes. (Similar remarks apply to negative integers.) In C, every value has a type. All the values we've been working with so far have had type int (there have been other values, but they've all been under the hood, as it were, not values we're concerning ourselves with). There are two major floating-point types in C. For the moment, I'll pretend there's just one, since the differences between the two are minor for our purposes at the moment. That type is `float'. This is another keyword, and it's used like int when declaring variables: float diameter; float weight; float ratio; float a, b, c; You can do arithmetic with floats using the same operators you use for arithmetic with ints: float a, b, c; ...set a and b... c = (a - b) / (a + b); You can input and output them with %g to scanf and printf, much as you use %d for ints. But be careful: if you pass a value of the wrong type for the format (if you use %d with a float, or %g with an int), you'll get garbage, and, while some compilers can catch the mistake, many can't, and many of those that can require you to specify an option to get them to do so. You can convert between floats and ints in either of two ways. You can do it by just assigning to a variable of the other type float length; float conversion_ratio; int pixels; pixels = length * conversion_ratio; or you can use what's called a cast, which is just a type name in parens placed before an expression, pixels = (int)(length * conversion_ratio) + 2; where, in the latter example, the addition is int addition, not float addition, because it's adding two ints. (Casts can be thought of as unary operators, with a different operator for each type.) You can also mix types in arithmetic: you can do arithmetic with a float and an int. When you do this, the int is automatically converted to float. This means you can write things like float distance; float middle; middle = distance / 2; (instead of something like "middle = distance / 2.0" where the constant is floating-point). This, this automatic conversion, is what I was talking about when I wrote, above, that you can sometimes use an integer constant as if it were a floating-point constant, but it is not restricted to constants; you can also write float sum, avg; int count; ... avg = sum / count; Recall the second exercise above? I asked you to try to find expressions that appeared to not work right. Some of the cases I was expecting some people to find were things like printf("%d\n",7/2); where you might have expected to see 3.5 instead of the 3 you'd actually get. This happens because arithmetic between two ints never converts the ints to floats, so 7/2 does integer division. Of course, when the result cannot be exact - as in the 7/2 example - something has to be done with the fractional part. C always discards the fractional part, what is often called truncation towards zero (because, when the result is not an exact integer, the integer you get is closer to zero than the mathematical result is). Even if you do something like 799/100 (which is mathematically 7.99, almost 8) you'll still get it truncated to 7. Try something like printf("%g\n",7.0/2.0); and you'll see you do get 3.5. Remember the warning above about mismatches between the % escape and the value, though; if you do printf("%g\n",7/2); or printf("%d\n",7.0/2.0); you'll get garbage. Some compilers can warn about such mismatches, but even those that can usually don't warn by default - you usually have to turn on some kind of option to get those warnings. -> Exercise: Write a simple program that scans input, does some tiny computation, and prints output, using floats. For example, you might take the diameter of a circle and print its circumference (multiplying by 3.141592653589793). ---------------- Now, I need one more piece in order to introduce the next thing I want to talk about. (Well, strictly, not to introduce it, but rather to make it useful.) It's time to partially de-simplify one of the simplifications above. I'm going to introduce a new wrinkle in variable declarations. Consider one of the examples above, copied here unchanged: 1 #include 2 3 int main(void) 4 { 5 int val; 6 7 scanf("%d",&val); 8 printf("%d squared is %d\n",val,val*val); 9 return(0); 10 } Now, here's the same program, with a small change: 1 #include 2 3 int val; 4 5 int main(void) 6 { 7 scanf("%d",&val); 8 printf("%d squared is %d\n",val,val*val); 9 return(0); 10 } If you try this, you'll find it works the same as before. So, what's the difference? Well, with what we know so far, the only difference you'll see is that, in this second version, val starts out holding zero rather than trash. But there are other differences, and what I'm about to introduce is something for which one of those differences is significant. It's time to introduce something new: functions. Let's suppose you're doing a graphics program and you want to draw a triangle. Let's further suppose that your graphics code requires pixel coordinates, but you have floating-point coordinates, so you need to convert. This is not difficult; if you need to just draw a point, you might do this with something like float xmul, xadd; float ymul, yadd; float fx, fy; int ix, iy; ...set up xmul/xadd/ymul/yadd for the transform... ...get your location in fx and fy... ix = (fx * xmul) + xadd; iy = (fy * ymul) + yadd; ...draw the point at (ix,iy)... But now, if you want to do a triangle, with what we know so far, you have to do something like float xmul, xadd; float ymul, yadd; float p1x, p1y; float p2x, p2y; float p3x, p3y; int i1x, i1y; int i2x, i2y; int i3x, i3y; ...set up xmul/xadd/ymul/yadd for the transform... ...get your triangle in p1x/p1y/p2x/p2y/p3x/p3y... i1x = (p1x * xmul) + xadd; i1y = (p1y * ymul) + yadd; i2x = (p2x * xmul) + xadd; i2y = (p2y * ymul) + yadd; i3x = (p3x * xmul) + xadd; i3y = (p3y * ymul) + yadd; ...draw your triangle... It's annoying to have to repeat the code for the coordinate transformation. Functions let you encapsulate things like that. Here's what that might look like, with just functions of the form I'm introducing now (which is not their full generality): float xmul, xadd; float ymul, yadd; float cvt_fx, cvt_fy; float cvt_ix, cvt_iy; void cvt_pt(void) { cvt_ix = (cvt_fx * xmul) + xadd; cvt_iy = (cvt_fy * ymul) + yadd; } int main(void) { float p1x, p1y; float p2x, p2y; float p3x, p3y; int i1x, i1y; int i2x, i2y; int i3x, i3y; ...other code... ...set up xmul/xadd/ymul/yadd for the transform... ...other code... ...get your triangle in p1x/p1y/p2x/p2y/p3x/p3y... cvt_fx = p1x; cvt_fy = p1y; cvt_pt(); i1x = cvt_ix; i1y = cvt_iy; cvt_fx = p2x; cvt_fy = p2y; cvt_pt(); i2x = cvt_ix; i2y = cvt_iy; cvt_fx = p3x; cvt_fy = p3y; cvt_pt(); i3x = cvt_ix; i3y = cvt_iy; ...draw your triangle... ...other code... } Moving the declarations outside of the int main(void) { ... } frame is important here. If we'd left those declarations inside main, inside that { } frame, then the code for cvt_pt wouldn't see them: the references to xmul, cvt_fy, etc, would have produced complaints about the variables not being declared. We could have put them inside cvt_pt, and then those references would have worked, but the ones in main would not have: declarations inside a { } block are not visible outside that { }. But declarations made outside all { } blocks are visible from the point of declaration all the way to the end of the file. In this particular case, this may not seem like much savings: we've replaced two lines with five. And that's a fair point. There are two respects in which this is a bad example from a practical point of view (I'm using it here to illustrate functions, and to motivate the next thing I'm going to introduce). I wanted something small; I didn't want to come up with a fifty-line example function just to motivate my next point. One is that the computation we're factoring out is quite small, just two lines, each of which is just one multiply, one add, and an assignment. If we were doing a lot more work inside cvt_pt (say, if we were searching a table), the savings would be greater. The other is that the only way we have to get the input values into and out of cvt_pt is to store them into and fetch them from those global variables (`global' is an adjective commonly used for variables declared, as above, outside functions). This method for passing data in and out works, in cases like this, but it's hardly ideal. However, it's a good example in that it motivates the next thing I'm going to introduce. In passing, you also may note a similarity between "void cvt_pt(void)" and the "int main(void)" we've been using all along. This similarity is not coincidental. There is a sense in which main() is a function like any other. (The difference between the leading "void" and "int" is one which I will explain shortly.) The braces that we've been considering as part of our framing all along are actually just the braces that surround any function body, in this case surrounding the body of main(). This leads naturally into the next point: getting data into and out of functions. This *can* be done as in the example above, by using global variables. Occasionally that's actually the best way, but those cases are fairly rare. Like most languages, C has a better way to pass data into and out of functions. Passing data into functions is usually done via parameters (also commonly called arguments, frequently abbreviated to args). These are variables declared as part of the function definition's header, the part before the opening { of the body. Passing data out of functions is usually done via the return value. (Terminological note: the term `parameter' can refer to a variable declared as part of the function definition, a variable used to hold an input value; it can also refer to a value passed in when the function is called. When it is important to distinguish the two, the former is typically called a formal parameter and the latter an actual parameter. Similar language is used with `argument', too; you may even see the adjectives used as nouns, as in things like "this is an error because there are fewer actuals than formals".) Here's a simple example of a function that passes data in via an argument and out via a return value: int square(int val) { return(val*val); } There is only one parameter here. It's called `val' and it has type int. (The declaration in parens is written the same as if you were declaring an ordinary variable, except that it goes in the parens and it has no semicolon after it.) The function's return value also has type int. (The int in the parens, just before `val', specifies val's type; the int outside the parens, just before `square', specifies the return type.) If there were multiple parameters, you'd write their declarations between the parens, with commas in between, as in int three_args(int arg1, int arg2, float arg3) { ... } This is also one circumstance in which you cannot declare multiple variables in a single declaration. If you try to write int three_args(int arg1, arg2, float arg3) (trying to put arg1 and arg2 into the same declaration), this will instead be taken as trying to declare arg2 with no type. What happens in this case will depend on the compiler - usually either you'll get an error or the compiler will supply the type int for the argument in question (typically with a warning message). I've mentioned passing multiple values into a function. This may lead you to wonder about returning multiple values from a function. In C, strictly speaking, there is no such thing. You can get much the same effect by returning a struct, but that will have to wait until we get to structs. There is an older syntax for defining functions. I don't recommend using it, but you will eventually run into it, so you should be prepared to recognize it. This so-called `old-style' variant puts just the parameter names between the parens, putting the declarations between the close paren and the open brace: int three_args(arg1, arg2, arg3) int arg1; int arg2; float arg3; { ... } There are a few differences between the two forms, even when they superficially look equivalent (as in the above examples); I'll go into those later. There are also some details of the way arguments are passed to a function which I need to discuss. I'll do this, below, when I talk about old-style declarations/definitions, since one of the biggest differences affects argument passing. It is possible for a function to take no parameters; this is specified by writing a single `void' between the parens, the place where the parameter declarations would normally go. And, it is possible for a function to return nothing; this is specified by giving `void' as its return type, and is sometimes spoken of as the function "returning void". This is what I did when first introducing functions: the syntax I gave specified both of these. (You may wonder why a function with no parameters has the `void' in the parens, instead of having nothing at all there. This is because of the historical legacy of old-style definitions: a definition with nothing at all in the parens is an old-style definition with no parameters. When I explain the differences between old-style and new-style function definitions, you'll see why it's important to draw this distinction.) We've already seen something that looks superficially similar to the statement that forms the body of this function: the return(0) that has been part of our boilerplate. return is actually a statement that, when executed, always does one thing and sometimes does another thing. The thing it always does is to cause the containing function to return immediately. It may or may not have an expression after it - the example above has one; you can also write return; without any expression. This is the optional thing it does: when it is followed by an expression, that expression's value provides the function's return value. (It is an error to execute a return statement with an expression in a function that returns nothing. Executing a return statement with no expression in a function that returns something is not an error in the sense of being forbidden by the language, but it is an error in that it means the value returned is undefined. If the calling code uses the return value, that's another case of undefined bahviour.) Many people tend to treat return, in a textual sense, as though the parens were part of the language's syntax for the statement, in the sense in which they are for while () or if (). There is nothing actually wrong with this from the language's point of view, but it is unnecessary - all it does is mean that the expression happens to have parens around it. The above example's return statement could equally well have been written return val*val; Of course, defining a function isn't much use without a way to make it execute. The term for doing this is to `call' it. You call a function by writing its name, followed by parens; if the function has any parameters, you put them between the parens. (This is a simplification; there are other ways to call functions, but they involve language features we haven't met yet.) If the function returns anything, then a call to it is an expression whose type is the function's return type and whose value is the value provided to the return statement that caused the function to return. (As I remarked above, if a return statement with no expression is what causes the return, the value returned is not defined and must not be used. And if control flow reaches the end of the function's { } block without any return statement of either sort happening, that is equivalent to a return statement with no expression, including what that means for the return value.) So, given the above `square' function, `square(7)' is an expression whose type is int and whose value is 49. We can use it anywhere such an expression is allowed. (Simplification alert: there are a few places where function calls are not allowed in expressions. We haven't met any of them yet.) For example, we could printf("7 squared is %d\n",square(7)); or we could do int sq; sq = square(7); or the like. Now, the "int main(void)" and "return(0);" in the boilerplate I gave you at the beginning should make sense. The implication is correct: main() is a function taking no arguments and returning int. (Simplification alert: there are actually multiple permitted ways to define main(); this is just the simplest one.) The name `main' is special in that execution of your program starts by calling the function by that name. If there is no such function, you'll get some kind of error when trying to compile your program. (Another simplification; under conditions I haven't yet described, program startup can happen in other ways. You are unlikely to meet any such conditions until you've been using C for a while; I'm going to outline them, but not until much later.) You also may have noticed that printf and scanf, as I described them, look a lot like function calls. This is not coincidence; that's exactly what they are. They're unusual functions in that they don't have a fixed number of arguments. I haven't yet touched on how to write such function; we'll get into that later. For the moment, it's enough to know that there is a way to define such functions, and that you don't need to know how to define such a function in order to write a call to it. We now have enough pieces to build useful, if limited, programs. Input and output, especially input, are still fairly rudimentary, and there are numerous things we haven't yet touched upon which make many things easier. ---------------- The next thing I want to introduce is another form of input and output. With what we've got so far - printf and scanf - you can print constant text and you can print numbers, both ints and floats, and you can read input numbers, both ints and floats. Next up is character-at-a-time input and output. These take the form of two more functions: putchar() and getchar(). getchar takes no arguments and returns an int; putchar takes an int and returns...well, actually, it returns an int, but for the moment we'll pretend it returns nothing. (All these functions - printf, scanf, putchar, getchar - have ways to return error indications; that's why putchar returns int. We haven't touched on handling I/O errors in any form yet, though, so the return values have been useless and I thus haven't discussed them.) putchar takes a single character and sends it to the output stream, the same place printf sends output. getchar just gets the next character from the input stream, the same place scanf reads input from, and returns it. Side note: there is a subtlety hidden in my use of `character' above. Traditionally, C has treated characters and bytes as being just two names for the same thing - or, more precisely, as different conceptual things that use identical implementations. This works fine as long as you're using ASCII, or a single-byte character set like one of the ISO 8859 family. It starts to break down when you deal with things like Shift-JIS; it gets worse when you deal with Unicode and encodings like UTF-8 or UTF-16. For the moment, we will take the traditional view, considering characters and bytes as two different conceptual points of view of the same implementation-level thing. There is a second subtlety hidden in the above paragraph. I have been casually speaking of `byte's. These days, a `byte' is nigh universally an 8-bit datum. But it was not always so; historically, bytes of various sizes all the way from 1 to 48 bits have been used, though 6, 8, and 9 bits have perhaps been the commonest sizes. When the difference matters, and sometimes even when it doesn't, the term `octet' is often used to describe specifically a datum made up of 8 bits, whether or not that is the size of a byte in any relevant context. Many of the networking protocol specs, for example, speak of octets and octet streams, to emphasize that the unit on the wire is 8 bits wide even if you're on something like a PDP-10 where bytes may be some other size (the PDP-10 is a 36-bit machine and 6 and 9 are perhaps commoner than 8 as byte sizes on it). For modern work, though, this is little but a historical curiosity; these days, `byte' and `octet' are effectively identical in almost all contexts. So, really, getchar() gets the next byte from the input stream and returns it. Assuming you're on a machine with 8-bit bytes (and you'd have to look relatively hard to find any other kind these days), it will return a value from 0 through 255 - or, under certain exceptional circumstances, the special value EOF (which has a negative value, usually -1, but for the sake of portability it is best to not depend on exactly which value it has). EOF is returned to indicate end-of-file when reading from a file, or end-of-input when reading from the user (what "end-of-input" means is OS-dependent; in interactive use, there typically is a special keystroke that will generate this, such as control-D in the default configuration of most Unix variants). It is also returned to indicate certain kinds of errors; I'll get into that later. For the moment, think of EOF as a variable which is declared for you. It actually isn't, but with what we know so far the differences aren't likely to be visible unless you try to assign to it. So, let's build about the simplest program we can with getchar(): 1 #include 2 3 int main(void) 4 { 5 int ch; 6 7 while (1) { 8 ch = getchar(); 9 if (ch == EOF) { 10 return(0); 11 } 12 printf("%d\n",ch); 13 } 14 } (There's one other thing I'm sneaking into this program. I'll let that cat out of its bag below, after I describe the program's primary operation.) When you run this, you will need to type input to it. But, unlike the programs we wrote using scanf(), it doesn't have to be numbers. Each character (byte, octet, etc) of your input will be printed back at you as a number. For example, if you're on a machine using ASCII, or an ASCII superset (and here, too, we have a case where the modern world has far less variety than has historically been present, with ASCII and its supersets now being overwhelmingly dominant), you could type Hello and it would print out 72 101 108 108 111 10 The first five lines aren't all that surprising - they're just the codes for the ASCII characters H, e, l (twice), and o - but what's that 10 at the end? That is the line break. Under most operating systems, you will have to type the whole line and press RETURN (or ENTER, or whatever it's called on your keyboard) after it before your program gets any of it. That 10 represents the RETURN (ENTER, etc). On a few systems, you may see 13 instead, or perhaps even a 13 and a 10 both, but a lone 10 is the commonest. To get out of this program, you will need to do something special, but what it is depends on your operating system. There are usually at least two things you can do; there usually is a keystroke you can type that will interrupt the program and kill it off ungracefully (control-C is probably the commonest form of this) and there usually is a keystroke that signals end-of-input (control-D is probably the commonest). And that leads into the piece I quietly introduced. Notice that, when getchar() returns EOF, the test on line 9 fires, and then line 10 does return(0). Well, we know what return(0) does, so what's the sneaky part? Well, there are actually two of them, but neither one is really all _that_ sneaky. One is the placement of the return(0): (a) it is conditional (it's happening under the control if an if()) and (b) it returns out of the middle of a while() loop. Neither part of this is unusual in practice; return()ing out of a function is one way you can break out of control structures like a while(). The other is that the test for the while is just a bare 1, not a comparison at all. The full details of what's going on here will have to wait until I talk about the details of how the comparison operators work. For the moment, just know that this is an idiom for an infinite loop - or, in this case, not really infinite, because line 10 can return out of the middle of it. I perhaps should have said, for a test that always passes. Similarly, there's an idiom for a test that always fails. That's 0, rather than 1. You might wonder what use either of those is. Why have a test that never changes? Well, here, you can see a use for a test that always passes - all of C's looping constructs have tests, so a test that always says "go on looping" is the closest thing. (Possible quibbles: there is a looping construct we haven't seen yet in which the test can be textually omitted (in which case it always passes) and it is possible to manually construct a loop without a test using other, more general, facilities I haven't talked about yet.) Tests that always fail are less useful, but I know of at least two or three uses for them, which I'll cover later. -> Exercise: Try this program. Try typing unusual things to it to see if you can get unusual results. As for putchar, that is just the converse of getchar: you pass an int to it and it converts it to a byte and sends that byte to the output stream. For example, assuming you're on a system where the above example produced the output I gave, then you could write putchar(72); putchar(101); putchar(108); putchar(108); putchar(111); putchar(10); and you'll get Hello as a result. -> Exercise: Build a program that will print out each character in whatever character set you have available. In ASCII, for example, the printing characters are character codes 32 through 126. (ASCII actually has 128 character codes, 0 through 127, but 33 of them - 127 and 0-31 - are not printing; they are so-called control characters, which correspond to special functions such as "backspace" or "ring bell" rather than actually printing.) If you're feeling adventurous, try other character codes, perhaps the control characters, perhaps characters outside the 0-127 ASCII range. Some of them may produce cryptic and/or confusing effects, depending on the details of your operating system and sometimes other things. It's a pain, though, to have to memorize - or constantly look up - all those character codes. But C has a way to save you from it! You can write, for example, 'j' to get the character code corresponding to the j character. That string of putchar calls above, for example, could have been written putchar('H'); putchar('e'); putchar('l'); putchar('l'); putchar('o'); putchar('\n'); where I've also introduced another thing: that last one looks as though it has *two* characters between the ' '. In a sense, of course, it does, but that's a so-called escape sequence: \ is special inside ' ', taking the next character (and sometimes more) and generating a single character code. \n, for example, generates the character code for a line break character (n stands for `newline', a name derived, via ASCII, from the function of starting a new line of output). You've actually seen this already, in a slightly different guise: every printf call I've shown you has had one. It's been between " ", not ' ', but, while there are certainly differences between ' ' and " ", they do not differ in escape sequences: the same escape sequences work for both. For example, if we go back to the very first program, and look at the printf call printf("Hello, world!\n"); That prints out 13 characters (the H through the !) and then prints a newline. On a typical Unix variant, for example, running that might look something like $ ./hello Hello, world! $ (where the $ is your prompt). If you remove the \n, you'll generally see, instead, something more like $ ./hello Hello, world!$ where, because you don't have the newline there, it doesn't go to a new line after printing out the "Hello, world!" text, so your prompt ends up tacked onto the end of that line. In this case that's undesirable, but an omitted newline can be useful if, for example, you want to build up a single line of output from multiple pieces: printf("Hello,"); printf(" world!"); printf("\n"); There are seven escape sequences that, like the above, have a single letter after the backslash: \a \b \f \n \r \t \v. Here's what they mean: \a "Alert": some kind of alert signal, typically a beep or a screen flash. \b "Backspace": backs up one character position, typically so the character following it will overwrite the one preceding it. \f "Form feed": skips to the beginning of the next page. When used on a computer screen, typically turns into a screen clear. \n "Newline": go to the beginning of the next line. \r "Return": go to the beginning of the current line. Usually equivalent to some number of backspaces. \t "Tab": advance to the next (horizontal) tab stop. C does not specify where tab stops are; in practice, much code assumes they are set every eight columns. \v "Vertical tab": advance to the next vertical tab stop. C does not specify where vertical tab stops are; I'm not aware of any de-facto standard for them. The commonest by far is \n, with \t probably in second place. After that are probably \r, \b, and \a, then \f, with \v a distant last (I can't recall ever seeing \v used "for real", ie, in something other than code to generate or consume escape sequences). Some compilers implement others; for example, I've seen \e standing for ESC. There are also four non-letter sequences that consume a single character: \\, \', \", and \?. In each of those three cases, the escape sequence simply produces the second character of the escape sequence. Except for \?, which is a workaround for an ugly misfeature (see below), they exist so that the characters they represent can be used inside ' ' and " ". For example, if you want to print He said, "No." you might write printf("He said, \"No.\"\n"); where the \" sequences allow you to put a " - which would otherwise end the string-to-be-printed - inside the string, and the \n is the usual end-of-line character. \' is not needed inside " ", but it is inside ' '; similarly, \" is not needed inside ' ', but is inside " ". (They're permitted regardless; "\'" is effectively the same as "'", for example.) And \\ is needed for each of them, or you couldn't refer to a \ inside " " or ' '. \? is related to trigraphs, a language misfeature I will talk about much later. You aren't likely to want it unless you write a string of multiple question marks and get something surprising, either an unexpected compiler message or unexpected output. Finally, there are two ways of getting arbitrary byte values. A \ may be followed by one, two, or three octal digits (0-7), specifying a single byte value by interpreting the digits as an octal number; or, a \ may be followed by an x, then a string of hex digits (0-9, a-f, or A-F), again, specifying a single byte value by interpreting the digits as a number, in this case as a hexadecimal number. In either case, if the number is too large for the byte size in use, what happens is unspecified, but usually you'll get just the appropriate number of low bits, dropping the others. So, again assuming ASCII, in which " is code 34, hex 22, octal 42, you could write printf("He said, \"No.\"\n"); as printf("He said, \42No.\x22\n"); but, of course, using \" is clearer and doesn't depend on " being code 34. (As I mentioned above, the latter is a practical issue in only a very tiny fraction of the live environments in use today; the former is a stronger reason in practice. It is, however, generally wise to preemptively avoid portability problems even when you don't expect anyone to ever run into them. Sooner or later someone will.) -> Exercise: Try to build `Caesar cipher' encoder and decoder programs: a historical (and by modern standards extremely weak) encryption system in which letters are replaced by letters three letters further along in the alphabet, wrapping around from the end of the alphabet to the beginning: a -> d, j -> m, w -> z, y -> b, and similar for uppercase letters. Decoding, of course, maps the other way. The encoder, for example, would turn Hello, world! into Khoor, zruog! or Sphinx of black quartz, judge my vow. into Vsklqa ri eodfn txduwc, mxgjh pb yrz. The decoder would reverse the process. -> Exercise: Now, try to do the same thing in a way that doesn't depend on the character set in use (except, of course, that it must contain the 52 letters, 26 each uppercase and lowercase; C promises that much, among other things). You do have the tools to do this; it's not even particularly difficult, though it is annoyingly tedious. The second of the above two exercises is not just an exercise for you to try to use the tools you've got. It's also motivation for some new things I want to introduce. There are two things that, collectively, make that last exercise much easier. The first of these three is another data type - or, more precisely, another family of related data types. Like most programming languages, C has ways to construct so-called `aggregate' types from what we might call its `base' or `primitive' types. C has three ways to do this. The first such way I'm going to describe is perhaps the simplest: the array. An array is a way of taking a bunch of objects, all of the same type, and collecting them together, with the particular one you're working with identified with a small integer. An array, viewed as a type, consists of a base type and a size. For example, you could have an array of 20 ints, or an array of 4 floats. In C, the thing that identifies the particular element of the array you're working with, the so-called subscript, is a small integer from 0 upwards, as many as there are elements in the array. So that array of 4 floats, for example, will have elements corresponding to 4 different subscripts. They are the first 4 integers, starting with 0: 0, 1, 2, and 3. (In C, array subscripts always start at 0. Some other languages do this too. Others start at 1. There even are some which allow you to specify the minimum valid subscript as well as the maximum. I don't know of any that start at a fixed value other than 0 or 1.) The syntax for declaring an array variable is to use [ ]. For example, if that array of 4 floats is to be called `location', we might write float location[4]; The syntax for using an array also uses [ ]: you put the subscript in the brackets, as in printf("%g\n",location[elt]); Subscripts must be ints. (There are two simplifications here. One is that [ ] is more general than what I'm describing. The other is that subscripts do not actually have to be ints; they have to be integers, and C has more integer types than just int, though I haven't described any of them yet.) A subscripted array is, effectively, a variable whose type is the element type of the array. Given the `location' array just above, we can treat, for example, location[2] as a variable - a float variable, because location is an array of floats. We can assign to it location[2] = 5.75; or print it printf("%g\n",location[2]); or use it in arithmetic distance = location[2] - loc0; The subscript - [2] in the examples above - does not have to be constant; it can be any integer-valued expression: distance = location[inx-base] * factor; Strictly speaking, C does not have multidimensional arrays. However, it is possible to have an array whose elements are arrays, which amounts to much the same thing: int corners[3][4]; This declares `corners' as an array of three things: ... corners[3] ...; But what are those things? Each one is an array of four ints: int ... [4]; Thus, corners is an array of three things, each of which is an array of four ints, amounting to a 3x4 array of ints. There is no practical limit on how deeply you can nest this syntax; if you're crazy enough to (or you actually find a real use for it!) you can declare int things[2][2][2][2][2][2][2][2]; to get an array of 256 ints, organized as an 8-dimensional array having size 2 in each dimension. (The implementation is permitted to impose a limit on the number of such dimensions an array has. If I'm reading C99 right, any such cap must be at least 12.) Incidentally, [ ] used for subscripting is an example of a binary operator which is difficult to classify on the prefix/infix/postfix trichotomy. This is because the operator textually has two parts, one of which goes in the infix position and the other of which goes in the postfix position. C even has one construct which can be thought of as a binary operator in four pieces: one prefix piece, two infix pieces, and one postfix piece; we'll get to it in due course. Array sizes are usually simple constants. They can be any integer constant expression, which means an expression with integer type and certain things forbidden. The only forbidden things we've seen so far are function calls. (Simplification: when the declaration is inside a function - actually, when it has automatic storage duration - this restriction is relaxed somewhat; for the time being, we'll ignore that.) The other of the two things I mentioned above is is something that's been lurking in the background ever since I mentioned that variables aren't set to anything in particular on startup (except that if they're declared outside all functions, they're set to 0). There is a way to specify that they're to be set to something. The usual verb for this is `initialize': the variable is said to be initialized and the value it's to be set to is called the initializer. The syntax for initializing a simple variable is a cross between declaration and assignment; it looks like = ; as in int nfound = 0; If you're declaring multiple variables in a single declaration, each one potentially has its own initializer, as in int a=0, b, c=2; where a and c are initialized to 0 and 2, respectively, and b is not explicitly initialized. (If this is inside a function, b is initialized to trash; if not, it's initialized to 0 - either way, the same as if it were declared all by itself.) I mentioned, a while ago, that expressions sometimes couldn't include function calls. One of the places they can't is initializers, but only some initializers - specifically, initializers for variables declared outside functions. (Simplification: it's actually objects with static storage duration, but that's a concept for later; restricted to what we've seen so far, it's equivalent to what I just said.) To initialize an array, the part after the = is more complicated than just a simple expression. It takes the form of { } with the initializer values between them, comma-separated. For example, we can write int factorials[8] = { 0, 1, 2, 6, 24, 120, 720, 5040 }; In this case, the first value initializes the [0] element of the array, the second one the [1] element, etc. It is an error if there are more initializer values given than there are array elements; if there are more array elements than values, trailing zero values are supplied. When initializing an array, there is one more thing: you don't have to specify initializers in order. You can specify which array element each initializer goes with. This is done with yet another use of [ ], this time inside the list of initializers. For example, the above initialization int factorials[8] = { 0, 1, 2, 6, 24, 120, 720, 5040 }; could be written int factorials[8] = { 0, 1, 2, [4] = 24, [7] = 5040, [3] = 6, [5] = 120, [6] = 720 }; The term for this is "designator". If you mix initializer elements with designators and without, the elements without follow on sequentially after the last designator, or starting from zero if there is no previous designator. Thus, another equivalent way to write the above example is int factorials[8] = { 0, 1, 2, [7] = 5040, [3] = 6, 24, 120, 720 }; If you specify multiple values for a single array element, the last-specified one is the one that gets used. If we write, for example, int factorials[8] = { 0, 1, 2, [5] = 99, [7] = 5040, [3] = 6, 24, 120, 720 }; then the first attempt to initialize the [5] element (to 99) is discarded, being replaced by the second one (the 120). If you're initializing an array-of-arrays, the same rules apply recursively. For example, int offsets[3][3][2] = { { {-1,-1}, {-1, 0}, {-1, 1} }, { { 0,-1}, { 0, 0}, { 0, 1} }, { { 1,-1}, { 1, 0}, { 1, 1} } }; This initializes offsets[0] to { {-1,-1}, {-1, 0}, {-1, 1} }, offsets[1] to { { 0,-1}, { 0, 0}, { 0, 1} }, and offsets[2] to { { 1,-1}, { 1, 0}, { 1, 1} }, where each of those has the same rules applied recursively. For example, offsets[2][1] is initialized to {1,0}, meaning that offsets[2][1][0] is 1 and offsets [2][1][1] is 0. (Simplification: you don't actually need all of the braces in all cases. The rules for exactly what happens if you skip some of them are more complex than I want to get into right now. Also, simplification: you can put one level of braces around the initializer for a simple, non-aggregate, value.) The above initializer actually contains a hidden illustration of an earlier point, the one about layout and readability. Compare the above initializer to this one, which is functionally identical: int offsets[3][3][2] = { { { -1, -1 }, { -1, 0 }, { -1, 1 } }, { { 0, -1 }, { 0, 0 }, { 0, 1 } }, { { 1, -1 }, { 1, 0 }, { 1, 1 } } }; I think you can see how much clearer the first version, the one with layout which matches the conceptual structure, is. There is another, subtler, point hidden here also. I expect some of you are thinking - or would be thinking, when starting to write some code - that you're not working with anyone else, so readability is not important. Very occasionally, this is actually true, but in almost all cases, even when working alone, you should write for readability anyway. Sometimes someone else will unexpectedly have reason to read the code, yes, but almost always, your future self is likely to need to read the code. When you've written only three programs, you probably will remember what you did in each of them - but if you've written lots of them, or if it's been a long time since you touched one, you probably will not remember everything, and readability will matter. -> Exercise: Try to do the second Caesar-cipher exercise above, only with arrays and initialization as tools. See if you can come up with a more compact program. This is another time when I'm going to actually provide an answer to an exercise. These techniques are important enough that I don't want you to miss them, even if you don't think of them yourself. As above, I strongly recommend you try it yourself before reading on. Let's start with just reading and writing input and output, leaving the transformation part unspecified: #include int transform(int c) { ... } int main(void) { int c; while (1) { c = getchar(); if (c == EOF) { return(0); } c = transform(c); putchar(c); } } This skeleton is usable for any of the Caesar-cipher exercises, with only the body of transform() needing to change. We could also put the transformation code inline, where I've written a call to transform(). In this case, there's little practical difference, but making conceptual boundaries match code-structure boundaries tends to lead to clearer programs. In this case, that means separating the "read, transform, print" mechanics from the details of the transformation. It's good to get into good habits even when they don't matter much because it makes it easier to exhibit them when they do. Using this skeleton, I would expect some people to solve the first Caesar-cipher exercise with something like int transform(int c) { if ((c >= 'a') && (c <= 'w')) { return(c+3); } if ((c >= 'x') && (c <= 'z')) { return(c-23); } if ((c >= 'A') && (c <= 'W')) { return(c+3); } if ((c >= 'X') && (c <= 'Z')) { return(c-23); } return(c); } (This actually brings up another point: it's perfectly fine to have more than one return statement in a function.) This works, if you're using ASCII, which I expect (almost?) all of you are. (There are other, mostly now historical, character codes, such as EBCDIC, where the consecutive letters aren't using consecutive codepoints, meaning code such as the above thus won't work.) The second exercise is harder. About the only way, with the tools we had up to that point, is something like int transform(int in) { int out; out = in; if (in == 'a') { out = 'd'; } if (in == 'A') { out = 'D'; } if (in == 'b') { out = 'e'; } if (in == 'B') { out = 'E'; } ...etc... if (in == 'Z') { out = 'C'; } return(out); } or possibly a variant using multiple return statements instead of the second variable. (You can see what I meant about it being tedious but not difficult.) But, now, with an initialized array, we have another way to do it: int xform[256] = { ['a'] = 'd', ['A'] = 'D', ['b'] = 'e', ['B'] = 'E', ['c'] = 'f', ['C'] = 'F', ...etc... ['z'] = 'c', ['Z'] = 'C' }; int transform(int c) { if (xform[c] != 0) { return(xform[c]); } return(c); } This is not _entirely_ charset-independent, in that the [256] specification for the xform array assumes a 256-element character set. It also assumes that none of the letters in question use character code zero (because a zero value in xform[] is used to mark values not initialized and that thus shouldn't be mapped), but that, C promises anyway. Fixing the [256] issue requires language features I haven't mentioned yet. This seems like as good a point as any to bring up another thing most languages have that C also has: comments. Most programming languages have a way to add (mostly) free-form text to a program, for the sake of people trying to read it. They're typically called comments, because they are just commentary on the code, not strictly part of the code itself. They are, by definition, ignored by the compiler (possibly excepting affecting warning messages). C has two kinds of comments. One kind begins with /* and continues up to the next */ following, even if that's multiple lines later. The other kind begins with // and continues up to the next end-of-line. Comments are useful primarily to explain something to the reader that's not obvious from the code. For example, a commented version of the previous code snippet might look like this: /* xform[] describes the character transformation: it is subscripted with the input character and the value is the output character, or, if the xform[] element is zero, the input character is not mapped and the transformed form should equal the input. This xform[] performs Caesar-cipher encoding. */ int xform[256] = { ['a'] = 'd', ['A'] = 'D', ['b'] = 'e', ['B'] = 'E', ['c'] = 'f', ['C'] = 'F', ...etc... ['z'] = 'c', ['Z'] = 'C' }; int transform(int c) { if (xform[c] != 0) { // There's a mapping for c - return it. return(xform[c]); } // There's no mapping for c - return c unchanged. return(c); } One kind of comment you will occasionally see is actually of negative value. It looks like x = 0; /* set x to zero */ where the comment simply echos the code. Such comments are, as I said, of negative value. They do not tell the reader anything the reader doesn't already know (at least not if the reader actually knows the language), they visually clutter the code, and introduce the risk of one being changed but not the other: x = 1; /* set x to zero */ I once saw a well-known (and actually quite good, for its day) programming book - I can't quite recall which one, or I'd name it here; Kernighan and Plauger, maybe? - say that if the code and the comments disagree, both are probably wrong. I would agree with that. Obviously, I do not recommend writing such content-free comments. If you really must comment such an assignment, comment the _meaning_: x = 0; /* restart search at the beginning */ or x = 0; /* we're back on the y axis */ or x = 0; /* no entries found yet */ or whatever. ---------------- For motivation for the next language feature I want to introduce, let's go back to a previous example. Here it is: #include int main(void) { int input; int total; int done; done = 0; total = 0; while (done != 1) { scanf("%d",&input); if (input == 0) { done = 1; } else { total = total + input; } } printf("%d\n",total); return(0); } There is a way to eliminate that `done' variable. It's another form of flow-control statement. Specifically, it's break; This causes an immediate exit from the smallest enclosing while loop. (Simplification: it also affects a few other control structures, none of which we've seen yet.) With a break statement, that example simplifies: #include int main(void) { int input; int total; total = 0; while (1) { scanf("%d",&input); if (input == 0) { break; } total = total + input; } printf("%d\n",total); return(0); } The done variable goes away entirely, since it served no function except to get out of the loop, a function that's now being served by the break statement. The else clause in the if goes away too, since the break exits the while immediately, without completing the current trip through the body. And the while test changes so it always succeeds, since exiting the loop is handled by the break instead. This is as good a place as any to mention the other flow-control statement that affects loops like this: continue; This skips the rest of the current trip through the loop but _doesn't_ exit the loop. (The loop may exit anyway, if the loop test fails, but that's not because of the continue statement; it's because of the test.) For example, if we want a program that says how many lines its input contains, we can do this with something like #include int main(void) { int c; int lines; lines = 0; while (1) { c = getchar(); if (c == EOF) { break; } if (c == '\n') { lines = lines + 1; } } printf("%d line(s)\n",lines); return(0); } But we could also write, instead, #include int main(void) { int c; int lines; lines = 0; while (1) { c = getchar(); if (c == EOF) { break; } if (c != '\n') { continue; } lines = lines + 1; } printf("%d line(s)\n",lines); return(0); } In this particular case, this is arguably a step backwards; if we did more code in the case where c is '\n', it would be less so. continue is more useful in cases like while (1) { ...get the next thing to process... if (some test) { ... if (another test) { // Don't want this one at all. continue; } ... } ... if (a third test) { // Nothing further for this one. continue; } ... } (Like break, continue affects more than just while loops, though, also like break, none we've seen yet.) ---------------- It's time to properly introduce something we've actually been using since the very first example program, but have never really explained: strings. Recall the first printf we ever saw printf("Hello, world!\n"); What is going on with the stuff inside " "? So far, we've seen them only in conjunction with printf and scanf. But there's more lurking there. To fully explain strings, we need a brief digression. You may recall that I said, a while back, that C had multiple integer types, not just int. It's time to expand on that. Specifically, one of those integer types is called `char'. (The name is an abbreviation for `character', not related to the English word "char". Some people pronounce it like "char" anyway; some pronounce it more like "car", and I've even heard "care", presumably because that's the first syllable of "character".) char is just like int, except the range of values it can take on is much smaller; it is designed for storing characters, or more precisely bytes (recall the discussion above about characters versus bytes versus octets). The reasons putchar takes int rather than char and getchar returns int rather than char are related to I/O error handling, something I still haven't talked about. A string in C is really just an array of char. (Simplification: this is for `string' meaning `thing written with " " around it'. `String' is also a technical term with a specific meaning that would take me too far afield to go into full detail on now.) The syntax with " " is just a convenient way of writing such a thing. If you write, let us say, printf("Hi!\n"); this is equivalent to char str[5] = { 'H', 'i', '!', '\n', 0 }; printf(str); except that there's no name for the array of char; the compiler counts the characters in what you wrote, appends a char with value 0 for reasons I'll get into later, and generates an anonymous array initialized to the result. (Simplification: the anonymous array also differs in that attempting to modify it - which we don't currently have any way to do, since it has no name - produces undefined behaviour, as opposed to well-defined behaviour for trying to modify str[] in the above.) Explaining strings in full gets us into talking about pointers, which I want to defer until later. I mention strings in large part because they are closely related to chars, which I want to start using. The above Caesar-cipher example - the one with xform[] - would actually have been written with xform[] being array-of-char in practice: char xform[256] = { ['a'] = 'd', ['A'] = 'D', I also want to introduce two more operators. These are unary operators, and are unusual in that they can appear either before or after their operand. They are ++ and --, and what they do is to add 1 to (for ++) or subtract 1 from (for --) their operand. However, the operand must be a variable (simplifcation: actually must be a modifiable lvalue). Like most operators, ++ and -- return values. If you write the ++ or -- before the variable, the value returned is the new value, the value after the increment or decrement; if after, the value returned is the old value, the value before the change. The mnemonic is that if you write the ++ or -- before the variable, the change happens before the value is returned; if after, after. In practice, the line from the example above lines = lines + 1; would be written as lines ++; or ++ lines; depending on the programmer's taste. (If, as here, you're not using the returned value, putting the ++ or -- before versus after the variable makes no difference.) This leads into other operators. Here's the full list of operators C supports. We have seen only about half of these so far; the ones we have seen are marked with "(seen)". [ ] (seen) (subscripting) ( ) (seen) (function call) . -> ++ (seen) -- (seen) (){} (compound literal) sizeof unary & unary * unary + unary - (seen) ~ ! (seen) ( ) (seen) (cast) binary * (seen) / (seen) % binary + (seen) binary - (seen) << >> < (seen) > (seen) <= (seen) >= (seen) == (seen) != (seen) binary & ^ | && (seen) || (seen) ?: = (seen) *= /= %= += -= <<= >>= &= ^= |= , (Arguably, ( ) for grouping should go on that list, but I'm not sure how fair it is to call them operators.) I'd like to describe those of them which we haven't seen but which are relatively simple at this point. (Simplification: there is a transformation that applies to most arithmetic operands, called the usual arithmetic conversions. We've ignored these so far; they matter only in corner cases.) Specifically, unary + This is an arithmetic operator and its operand must have arithmetic type (so far the only non-arithmetic types we've seen have been arrays). It has no effect on the value of its operand - it returns its argument unchanged. ~ This requires an integer operand. The result is the operand with each bit flipped, that is, all 0 bits converted to 1s and vice versa. % This is integer remainder: it is just like integer division using / except that it returns the remainder rather than the quotient - in the absence of errors, a%b equals a-((a/b)*b). (`Errors' here refers to cases like b being zero.) Since / on integers truncates towards zero, this means that the result of a%b either is zero has the same sign as b. << >> These are integer shifts. The left-hand operand is bit-shifted left (for <<) or right (for >>), with the right-hand operand specifying the number of bits it is to be shifted by. If the right-hand operand is negative or is at least as large as the number of bits in the left-hand operand, the behaviour is undefined. (Simplification: there are subtleties in >> that I can't explain until we talk about signedness of integers.) binary & This is bitwise AND: the operands must have integer type and the result is an integer with a 0 in each bit position where either operand has a 0 and a 1 where both have 1s. ^ This is bitwise XOR: the operands must have integer type and the result is an integer with a 1 in each bit position where the operands' bits differ and a 0 where they are equal. | This is bitwise OR: the operands must have integer type and the result is an integer with a 1 in each bit position where either operand has a 1 and a 0 where both have 0s. *= /= %= += -= <<= >>= &= ^= |= These are arithmetic-and-assignment operators. Each of them corresponds to a binary operator, spelled the same but without the = sign. If (op) represents the binary operator, then a (op)= b is equivalent to a = a (op) b (Simplification: equivalent except that a is evaluated only once; we haven't yet seen anything appear on the left-hand side of = for which this matters, though you do have the tools to build such a thing.) For example, instead of total = total * 3; you would normally see total *= 3; , This evaluates its left-hand operand and throws away the resulting value; it then evaluates its right-hand operand and returns the result. (Obviously, this is useful only when the left-hand operand has side effects, such as a function call. I'll discuss side effects more later.) ?: Above, I mentioned that there are two kinds of operators, unary and binary, with a simplification note that there is actually a third kind. This is the third kind: it is a ternary operator, one with three operands - the only ternary operator C has. If X, Y, and Z are three expressions, X ? Y : Z is an expression which evaluates X (the `test') and then, if the test passes, evaluates Y, otherwise evaluting Z, and returns whatever the evaluation of Y or Z, whichever one got evaluated, returned. (Some languages have nilary operators - those with no operands at all - or those with more than three operands. C does not have either, unless you treat function calls as operators, or treat constants as niladic operators.) One implication of the above which may not be obvious on first reading is that assignments are expressions. I presented assignments as a kind of statement, but the truth is that they're a kind of expression, and that any expression can be converted to a statement by tacking a ; on after it. (This, incidentally, is how a printf `statement' works: it is actually a function-call expression, converted to a statement by tacking a ; on after it.) If you use the value of an assignment expression, the value you get is the value that got assigned (which is not necessarily the same as the value you tried to assign, though we haven't gone into depth about how it can be different). We'll see some examples of this; I'll point out the first time or two we see it. In addition, I think it's time to explain how the comparison operators (<, >=, !=, etc) work. I've glossed over exactly what they return, simply speaking of tests passing or failing. They actually return ints, 1 if the test passes and 0 if it fails, and any int expression can be used as a test expression; any int other than 0 passes, while 0 fails. (Actually, anything that can be compared to zero can be used, so any type we've seen so far is acceptable - even arrays, for technical reasons I can't explain until we get to pointers.) This is why 1 and 0 work for always-pass and always-fail tests: they are int-valued expressions that always (for 1) or never (for 0) compare unequal to zero. You could write "while (74)", or any other nonzero int, instead of "while (1)"; using 1 is just the traditional way. This also leads to a pitfall. In ordinary mathematics, a < b < c is true if b is greater than a and less than c. But, in C, a 2 #include 3 #include 4 5 int secret; 6 7 int main(void) 8 { 9 int guess; 10 11 srandom(time(0)); 12 secret = random() % 100; 13 while (1) { 14 printf("Your guess? "); 15 scanf("%d",&guess); 16 if ((guess < 0) || (guess > 99)) { 17 printf("Guess out of range 0-99.\n"); 18 continue; 19 } 20 if (guess < secret) { 21 printf("You're low.\n"); 22 } 23 if (guess > secret) { 24 printf("You're high.\n"); 25 } 26 if (guess == secret) { 27 printf("You got it!\n"); 28 break; 29 } 30 } 31 return(0); 32 } The new things here are lines 1 and 3, both calls on line 11, and the random() call on line 12. Semi-new is the % operator on line 12; that's one of the list of operators I explained briefly above, but it's not one we've used before. Also semi-new is the lack of \n on line 14. Lines 1 and 3 are preprocessor directives. They are similar to line 2, which we've seen all along. I'm going to explain them just a little bit now, deferring a full explanation until later: each of these lines is a way of providing a bunch of declarations. For example, the "#include " we've seen all along, and see here on line 2, is a compact way of providing declarations for printf(), scanf(), getchar(), putchar(), and a bunch of other calls we haven't yet met. (#include lines can, and typically do, also do other things we haven't yet touched upon. In the case of , for example, it is also responsible for setting up EOF, which as I mentioned above works much like a variable declared for you, provided you don't do certain things, such as trying to change it.) This program uses three calls we haven't seen before: on line 11, it calls time(0) and then calls srandom() with what time(0) returned, and on line 12, it calls random() and does arithmetic with the result. We haven't seen any of these three calls before, and they need to be declared. We could write declarations for them, but when - as here - they are standard or quasi-standard and there's a #include line that can declare them, it's usually better to use the #include. We have two new #include lines because there's no single #include that covers all three of these new calls. random() and srandom() are part of ; time() is part of . The program won't make much sense without knowing something about what these three new calls do, though. The most important, from a conceptual point of view, is random(). Since we want our program to pick a random number, we need to get a random number from somewhere. random() is a library routine to generate random numbers. (They aren't random in the theoretical sense, only pseudo-random. And they're not strong enough randomness for cryptographic purposes. But we don't need either of those here; we just need a number that, as far as the human user goes, is unpredictable, and random() does nicely for that.) But, if we just call random(), we'll get the same sequence of numbers every time we run the program. It would be dreadfully boring if every time we ran the program it picked 83. So there is also srandom(), which causes random() to generate a new sequence of random numbers based on its argument (the so-called `seed'). We're then left with the problem of selecting a seed. The time-of-day is one of the traditional ways of doing this; time() returns the time of day, in a form that can be passed directly to srandom(). (The 0 argument is a historical artifact; for the moment, just think of it as part of the incantation to get the current time.) We could just use the time-of-day as our random number, instead of messing around with srandom() and random(). But if we run the program and it picks 27, and then we start it again 30 seconds after the first time we started it, it'll give us 57 (or maybe 56 or 58, if the "30 seconds" isn't quite exact). That's fine for some purposes, but hardly ideal for this program. Using srandom() and random() means that the relationship between the time-of-day and the number picked, while strictly speaking determinstic, is complicated enough that it's unpredictable from the human-user point of view. random() returns a random number from, typically, 0 through 2147483647. But we want a number 0 through 99. There are two major possibilities for reducing the range: we could divide our random number by 21474837 and use the quotient, which will result in a value from 0 through 99, or we could divide our random number by 100 and use the remainder, which will also result in a value from 0 through 99. As you may recall from the list I gave, % is the operator that divides but returns the remainder, rather than the quotient. The usual way to do range reduction like this is to use the remainder. There are three reasons for this (or four if you count "that's the way it's usually done"). The first is that using the quotient requires us to know the exact range of the random numbers we're generating. In this case, random() is documented (at least on my system) as returning a number in the 0-to-2147483647 range, so it's not hard to work out that 21474837 is the best number to divide by (see next paragraph). But if we change to using a different random number generator instead, say, one that produces numbers from 0 through 16777215, then we need to change the divisor to match. If we divide and take the remainder, we don't need to change the arithmetic at all. The second reason is that using % 100 instead of / 21474837 spreads out the unevenness better. Because the number of possible results from the random number generator isn't a multiple of 100, we can't have each possible result exactly equally likely. If we divide and use the quotient, out of the 2147483647 possible cases, we'll have 0 through 98 equally likely, with 99 less likely, by an amount depending on the divisor. Any number from 21474837 through 21691754 will give us a quotient ranging from 0 through 99, but we want the possibilities to be as close to equally likely as we can, so we want to use the smallest divisor possible. In this case that makes things fairly evenly balanced; out of 2147483647 possible values, there are 21474837 possibilities that give us each quotient from 0 through 98, with 21474784 giving us quotient 99. That's pretty good, certainly good enough for the purposes of this program. But, if we divide by 100 and take the remainder instead, then we'll have 21474837 cases giving us each result from 0 through 47 and 21474836 cases giving us each result from 48 through 99. This is, for most purposes, better, though admittedly for this program the difference isn't enough to really matter. (If we use the quotient with a larger divisor, the chances will get less and less balanced, all the way to using 21691754, which gives us 21691754 chances each for 0 through 98 and only one chance for 99, making 99 almost impossible. That's the sense in which we would want to use the smallest divisor possible.) The third reason is that this sort of technique isn't always done with numbers straight from a random number generator, and, even when it is, some systems' random number generators are rather poor quality. It turns out that most of the ways of getting numbers for range reduction get spread out across the possibilities better when using the % method than when using the / method. (For example, suppose we thought we were getting numbers from 0 through 2147483647, but on a new system we actually get numbers from 0 through 65535. If we use / 21474837, all possible values will end up giving us 0. But if we use % 100, we'll still get all 100 possible values - not quite as evenly balanced as if the range were 0-2147483647, but close enough for lots of purposes, about as close as we can get. And if by some mischance we think our random number generator generates 0-2147483647 and it actually generates 0-4294967295 instead, using the / method suddenly starts generating values 0 through 199 instead of 0 through 99, while using % still gives us what we expect.) Thus, using the % method is better from all three points of view, so that's the one we go with. The lack of a newline in the printf on line 14 means that the printed string won't have a line break after it. If the user types, say, 50, it will look like Your guess? 50 instead of Your guess? 50 This is commonly done when, as here, the printout is prompting for input. (The last space in this string is to provide space after the question mark. Without it, we'd see something like Your guess?50 instead.) Here's what I see from one example run: Your guess? 50 You're high. Your guess? 25 You're high. Your guess? 12 You're high. Your guess? 6 You're high. Your guess? 3 You're low. Your guess? 4 You're low. Your guess? 5 You got it! Here's another: Your guess? 50 You're low. Your guess? 75 You're low. Your guess? 87 You're high. Your guess? 81 You're low. Your guess? 84 You're high. Your guess? 82 You got it! That's the preparation. Now for the exercise. -> Exercise: Build a program that plays the guessing side of this game. The user has to pick a number, but not tell the program what it is. The program then has to guess numbers and let the human tell it high, low, or exactly right (probably by entering something like h, l, or e). I recommend you also try to make the program robust, in the sense that it does something sensible in case the user gives inconsistent answers, as in ... I guess 60. Is this h (high), l (low), or e (exact)? h I guess 58. Is this h (high), l (low), or e (exact)? l I guess 59. Is this h (high), l (low), or e (exact)? h Cheater! You told me 58 is too low but 59 is too high! or picks a number outside the 0-99 range, as in ... I guess 98. Is this h (high), l (low), or e (exact)? l I guess 99. Is this h (high), l (low), or e (exact)? l Cheater! Your number has to be in the range 0 through 99! or replies with nonsense, as in I guess 21. Is this h (high), l (low), or e (exact)? k Let's try that again. I guess 21. Is this h (high), l (low), or e (exact)? You may or may not succeed, but, as I've remarked above, the important part is what you learn in the process. I'm deliberately not telling you _everything_ you'll need here, though I've at least _mentioned_ everything you need. You have all the tools. ---------------- Next thing I'd like to introduce is most of the remaining flow control constructs. We've got if() (and if()-else) and while(). There are four more - well, strictly, three more, because one of them is just an idiomatic way of using one of the ones we've already got. The idiomatic one is a way of using if-else. You may see code structured like if (test) { ... } else if (another test) { ... } else if (third test) { ... } else if (fourth test) { ... } else { ... } This is exactly what it looks like: evaluate the tests in order, executing the body for the first one that passes. This really is just a slightly different way of formatting something we already know. To rewrite the above using what we already know, it would be if (test) { ... } else { if (another test) { ... } else { if (third test) { ... } else { if (fourth test) { ... } else { ... } } } } but the first layout (a) is closer to how people tend to think of it and (b) prevents long chains from walking off the right side of the screen. It's one of the few cases where I would suggest that even at this point you skip the braces around a single-statement body. Because that's really all this is; the first form is just the same thing as the second form with some of the braces stripped and the indentation changed. If you're particularly observant, you may recall that I spoke of being able to omit the braces around something like an if-controlled body if it's made up of a single statement, which implies that that's what's going on here. That implication is correct: the whole construct if (test) { ... } else { ...} is a single statement, and, in fact, the two blocks I've been telling you to put braces around are actually single statements too, once the braces are included: put braces around some statements and you turn them into a single (compound) statement. ---------------- There are many ways of constructing statements in C. We've actually seen seven of them already, though I haven't made it completely clear that that's what they are. The first, and possibly the commonest, is the expression statement. As I mentioned above, any expression can be turned into a statement by appending a ; to it. For example, printf("Hello, world!\n"); is an expression statement, where in this case the expression takes the form of a simple function call. As another example, total = 0; is an expression statement, where the expression is an assignment expression. You can make a statement out of any expression (length + 7) / 2; but unless the expression has side effects, such as changing a variable or printing some output, there's no point, and some compilers will warn about such pointless statements. (Side effects actually are a formal part of C, but I'll defer the full description of them for a bit yet.) Another one we've seen is the if statement. This has two forms: if (expression) statement if (expression) statement else statement Note the lack of braces. This leads into the third form, the compound statement: { statements } (Simplification: compound statements can contain more than just statements. I'll get into that later.) Thus, what I was describing as an if statement if (expression) { statements } else { statements } actually is technically an if statement where the controlled statements are themselves compound statements. Next is the while statement: while (expression) statement Once again, the braces around the body actually just mean that the body statement is a compound statement (which it usually but not always will be in practice). The final three statements we've seen are fairly simple: continue; break; return; return expression; (I'm counting these as only three because the last two are usually considered just variations on the same thing, and, in a lot of respects, they are.) This leaves four kinds of statements to talk about, four kinds we haven't yet met. One is the do-while statement. You may recall that, above, I mentioned that while loops are top-tested and that there is also a bottom-tested loop. The bottom-tested version is the do-while statement: do statement while (expression); Note that the expression comes after the body, which is a useful textual reminder that it is not executed until the body has run the first time. It is otherwise just like a while() loop: the body is executed repeatedly until the test fails. As an example of a do-while statement, suppose we want to discard the rest of an input line: int c; ... do { c = getchar(); } while ((c != EOF) && (c != '\n')); (Note that I'm comparing c against EOF as well as against '\n'. The reason for this will become apparent when I talk about I/O errors.) In this case, it's important that we read a character before we try to do anything based on it. (If we've already got a character read in, a while loop may be more appropriate.) break and continue statements work the same way in do-while loops that they do in while loops: they break out of the loop or skip to the end of the body (and thus the beginning of the test), respectively. The next is the third form of loop: the for statement. for (expression; expression; expression) statement Let's label the parts for expository clarity: for (expr1; expr2; expr3) body Except for one detail, this is equivalent to expr1; while (expr2) { body expr3; } The detail is that a continue statement in the body works differently. In the while form, a continue statement would skip expr3, but in the for form it actually skips to the end of the body, to the point that, in the while rewriting, is right between the body and expr3. (A break statement will break out of a for loop same as it would out of the while version of it.) Note that we don't have to think about what happens with break or continue statements in expr3, because expressions can't contain statements. (There is at least one popular compiler that does provide a way to make expressions contain statements. I don't know how it handles this case, and it doesn't matter here because I'm trying to teach C, not compiler-specific mutant C variants.) In a for loop, you can actually omit any or all of the expressions. If you omit expr1 or expr3, those parts simply disappear; if you omit expr2, it is as if you had specified a nonzero integer constant there (the test always passes). Simplification: in recent versions of C, for loops can get a bit more complex than this. I'll discuss that later. The third of these kinds of statements is the switch statement. Its formal syntax looks deceptively simple: switch (expression) statement The reason I call this deceptive is that, for this to be useful, the body statement has to have labels within it, which the switch can jump to. A switch stateament in practice looks something like switch (selector) { case value1: code for value1; break; case value2: case value3: code for value2 and value3; break; ... default: code for "none of the above"; break; } You may note the break statements. break statements serve to not only break out of looping constructs, but also switch constructs. (In contrast, continue statements ignore switch constructs.) The break statements are not required, but, if you don't use them, execution will flow right on from one case into the next. Occasionally this is what you want, but more often it's not - I think C would have been better designed if it had defaulted the other way, with a break implicit in a case label, with some explicit way to indicate that fall-through is desired. (The commonest use of fall-through is probably illustrated above, with "case value2" - it does nothing but immediately falling through to the following code, thus doing the same thing for value2 and value3.) You don't have to have a default: label. If you don't have one, then a selector expression value that doesn't match any case label will skip the whole body, executing none of it. (The default label doesn't have to be at the end, either, though it often is either at the end or the beginning.) The case labels do not have to be at the top level of the body statement the way they are here; they can be embedded at deeper levels, as in switch (expr) { case 1: if (test) { ...code X... case 2: ...code Y... } break; case 3: ...code Z... } This example code will test expr's value and then... - If it's 1, check test. If test fails, nothing more happens. If test succeeds, it then executes code X and then code Y. - If it's 2, executes code Y (without checking test or executing code X). - If it's 3, executes code Z. - If it's anything else, does nothing (no default: present). Like many features of C, this can be used to clarify otherwise obscure flow control; it can also be used to obscure otherwise clear flow control. Of course, you will usually want to opt for whichever way of writing the code is clearer. This is as good a point as any to remark on how some ambiguities are resolved. break and continue statements affect the smallest enclosing construct that's of an appropriate type (for, while, or do-while for continue; for, while, do-while, or switch for break). case and default labels are associated with the smallest enclosing switch statement. An else is associated with the closest if that the syntax permits (the closest one that's at the same nesting level and has not yet had an else attached to it). The fourth kind of statement is the great ugly escape clause: goto identifier; This causes an immediate jump to a label, elsewhere in the same function, taking the form identifier: with the same identifier. I strongly recommend that you not use gotos; they are occasionally necessary, but you will almost always be better served by one of the alternatives. Sometimes a goto will be used to break out of multiple loops, as in for (x=0;xeft ), but it is now perhaps better considered as a "locator" for a stored value, a way to find a stored value, either to read it out or to change it. The simplest example of an lvalue is a simple variable. Other examples are the things I've been describing as counting as variables: elements of arrays, elements of structs (at least lvalue structs), and what you get when you follow a pointer. So, just as you can store into a variable variable = 3; you can store into an array element arr[7] = 12; or (I haven't discussed the syntax for this yet) an element of a struct pt.x = 0.5; or the result of following a pointer: *ip = -1; In each case, the left-hand side of the assignment is an lvalue. You can use lvalues in other contexts, where you want to read their values rather than change them; just as you can store values into variables or other lvalues, you can also get them back out again (which is fortunate, or they wouldn't be much use). In general, wherever I've written of variables, above, it's really lvalues I'm writing of. For example, we can apply ++ to a simple variable ++ variable; or an array element arr[7] ++; or a struct element pt.x ++; or the result of following a pointer ++ *ip; There is a subtlety in one of the above examples (actually, two, in a way; see below). I wrote ++*ip, not *ip++. That's because, the way the C precedence hierarchy is defined, *ip++ actually means *(ip++), not (*ip)++, because ++ binds tighter than * does. (But array subscripting binds tighter than ++, so ++arr[7] means ++(arr[7]), not (++arr)[7] - that's why the arr[7]++ example only sort-of counts as a second subtlety.) Of course, you can write (*ip)++, with the explicit parentheses, if that's what you mean. There is also a caveat: applying & to variables, where the variables are declared inside functions, carries a risk: the pointer can survive longer than the variable it points to, at which point you have a bug waiting to happen. I'll talk more about this below. Pointers are also closely related to arrays. In particular, there are no array values - if you use an array in a context where a normal object would get converted to its value, an array gets converted to a pointer to its [0] element. Thus, when we write t = arr[7], arr actually gets converted to &arr[0] - subscripting really works with pointers, not arrays. Furthermore, when you add an integer to, or subtract an integer from, a pointer, it moves the pointer by that many objects of the type it points to. This is why arr[7] refers to element 7 of arr: arr[7] actually means *(arr+7). arr gets converted to a pointer to its [0] element; adding 7 to that moves 7 elements further along, resulting in a pointer to the [7] element of arr. You may wonder, why bother with all this? Principally, because it means that we can use subscripting notation with pointers as well as arrays. When I get to allocated storage duration, something I haven't yet talked about, this will become particularly important. So, what good are pointers? There are many uses. But, given what we know now, they're mostly useful when you want to operate on a stored value without caring where it's actually stored. For example, let's suppose we are using integers to store a number in the high bits and a shift count in the low three bits, and we want to shift the number right by the count in the low three bits, without disturbing the low bits. If the value is stored in a variable v, we can do this with v = ((v >> (v & 7)) & ~7) | (v & 7); (which takes the value, shifts it right, then masks off the low three bits, and ORs in the low three bits from the original value). But, if we want to encapsulate this in a function, we have the problem of how to refer to the variable, since we may want to use it on different variables, or maybe array elements or something. So, we can make it a pointer instead: void shift_val(int *p) { *p = ((*p >> (*p & 7)) & ~7) + (*p & 7); } and we can call it on one variable shift_val(&v); or another shift_val(&v2); or an array element shift_val(&vec[i+j]); and shift_val doesn't need to know or care what sort of thing it's operating on. This is particularly useful with structs. Suppose that, instead of an integer with the low three bits treated specially, we have a struct struct shifted_val { int val; // the value int sc; // the shift count }; and then we wish to perform the analogous operation. To do this, we need to use an operator I mentioned in passing above, but now need to introduce fully, to access structure elements. The operator is ., and it takes an object with struct type as its left-hand operand and a member name as its right-hand operand, and the result is that member of that struct. (The result is an lvalue exactly when the left-hand operand is: x.y is an lvalue exactly when x is. Also, simplification alert: . is also usable with unions, which we'll get to later.) Given the above struct shifted_val, if we have struct shifted_val v; we could do v.val >>= v.sc; (recall that a >>= b is, approximately, an abbreviation for a = a >> b). Now, let's suppose we want to encapsulate that: void shift_val(struct shifted_val *vp) { (*vp).val >>= (*vp).sc; } This works, but, because . binds tighter than *, we have to write those annoying parens; *a.b means *(a.b), not (*a).b. C provides a nicer-looking notation for this: vp->val >>= vp->sc; Think of a->b as just another way of spelling (*a).b. This example is simple enough that there's little point in encapsulating the operation. But, if we were doing something more complicated, it would matter more. Let's go back to an example from back when I first started to introduce functions, and rewrite it with a wider toolkit: struct point { float x; float y; }; float xmul, xadd; float ymul, yadd; void transform_point(struct point *p) { p->x = (p->x * xmul) + xadd; p->y = (p->y * ymul) + yadd; } This is far more usable. We can do something like transform a triangle with struct point vertices[3]; ...set up vertices[]... transform_point(&vertices[0]); transform_point(&vertices[1]); transform_point(&vertices[2]); or even struct point vertices[3]; int i; ...set up vertices[]... for (i=0;i<3;i++) transform_point(&vertices[i]); We could even go further, with something like struct scaling { float xmul, xadd; float ymul, yadd; }; struct point { float x, y; }; void scale_point(struct point *p, struct scaling *s) { p->x = (p->x * s->xmul) + s->xadd; p->y = (p->y * s->ymul) + s->yadd; } so we can apply different scaling to different points. Why would we bother with making scaling a separate function, when the scaling is such a simple operation? There are two major reasons. One is that, unless the operation is truly trivial, far smaller than this example, we will usually save code space by doing so: the code to call the function is probably smaller than the code to perform the scaling. There is some overhead from the function itself, but if we call it more than two or three times, we will probably save space. (We will lose a little bit of time; if every last cycle of performance matters - which it very rarely does - you might want to write some code inline rather than putting it in a function. See also the discussion of inline functions, below, and of preprocessor macros, also below.) The other is that it turns it into a conceptual atom, a thing we don't need to break down when reading the code. This has two advantages. One is that, when reading the code, if we see scale_point(&pt,&s); we don't need to read over the details and make sure the arithmetic is doing what we expect (or, if it's not, wonder whether it's a bug introduced by a typo or whether the coder really meant _this_ one to be different). The other one is that we can change it easily. Suppose we discovered that we needed full linear transformations instead of just scaling and translation on the axes independently. We could just elaborate struct scaling (I've renamed it, here, as well, because it's no longer just scaling, and I'm showing only the new struct and function) struct transform { float xx, xy, x; float yx, yy, y; }; void transform_point(struct point *p, struct transform *t) { p->x = (p->x * t->xx) + (p->y * t->xy) + t->x; p->y = (p->x * t->yx) + (p->y * t->yy) + t->y; } and the code to actually apply the transforms needs fewer changes - no changes, if we were forward-thinking enough to use names with "transform" instead of "scaling" from the start. (As far as C is concerned, we could go on calling it "scaling", but, as far as C is concerned, we could equally well call it "zucchini". The reason for using meaningful names like "transform" at all is that they mean something to people reading the code.) The bad reputation pointers have comes mostly from two things. One is that a lot of people, and some books, confuse the idea of pointers, the conceptual thing they represent, with one common implementation of them: they speak of a pointer to an object as its `address'. This then leads people to think that a pointer is an address not only in the sense of "this is how it's implemented on this machine", but also in the sense of "this is just another name for the same thing". They then start thinking they can do anything with pointers that they can with memory addresses and get into much trouble when they run into the differences between pointers and addresses, typically on a machine of an architecture they're not used to. (This tends to happen particularly with people who come from a background where they've worked in assembly, usually on only one kind of machine.) The other is that C is, as I put it way back at the beginning, a training-wheels-off language. There are some facilities that other languages provide that C doesn't; in this context, the biggest relevant one is probably memory management. In many higher-level languages (such as Python, Ruby, or Lisp) you don't need to bother yourself with issues like allocating memory to store data structures in, or freeing it up once you no longer need it. In C, you do. (The first versions of C were designed for implementing an operating system and were thus too low-level to have such facilities, and modern C has kept most of that.) This means, of course, that you can make mistakes with managing memory, and lots of people do. Anyone who claims to have never made a memory-management mistake either hasn't worked in C much or is outright lying. C provides library calls you can use to allocate and free memory, but it doesn't - can't, even in principle - check that you're using them correctly. The reason this is related to pointers is that you can't do this kind of dynamic memory allocation and freeing without using pointers - not in C, certainly. ---------------- Before we get into dynamic memory allocation and related issues, let's try another exercise. Let's read in the source to a C program and count all occurrences of the various keywords I listed above: auto, break, case, char, etc. For the purposes of this exercise, let's ignore the question of whether a textual occurrence of such a keyword is actually a language keyword (as opposed to being, say, part of a string's contents). There's one minor language feature I'll need to introduce to write this naturally, that being a minor aspect of initializing arrays. As I described array initialization, to initialize an array of five chars to, say, "hello", you'd need to write something like char array[5] = { 'h', 'e', 'l', 'l', 'o' }; (As I mentioned above, where I first talked about strings, "hello" is actually an array of _six_ chars, because a hidden 0 terminator gets added, but nothing says we can't define a similar array without the 0.) For char, specifically, there is a shorthand for this; we can write char array[5] = "hello"; This is restricted to chars because that's what " " strings are made up of. (Simplification: there are also wide strings, something we won't be dealing with for some time.) We can also write char array[6] = "hello"; to get the same thing as char array[6] = { 'h', 'e', 'l', 'l', 'o' }; As usual, if some members of an array are not explicitly initialized (as in this example), they are initialized as if to 0, so the above is equivalent to char array[6] = { 'h', 'e', 'l', 'l', 'o', 0 }; and char array[10] = "hello"; gives us the same thing as char array[10] = { 'h', 'e', 'l', 'l', 'o', 0, 0, 0, 0, 0 }; We'll need to read input (a character at a time, because the only other way we know to read input works only when reading numbers), accumulating characters of identifiers until either we end the identifier (at which point we can check whether it's a keyword or not) or until we find we have an identifier that's longer than the longest keyword (and thus obviously isn't a keyword). We could have special-case code for each language keyword, but it's usually cleaner to do it data-driven, which in this case means having a table of keywords and checking input against that table. (This usually results in smaller code and almost always makes it easier to add or remove items from the list - in this case, the list of keywords.) I'm going to describe the evolution of the program. This includes starting out with things which are going to get changed. I'm trying to give you some idea how a program might evolve in practice. Let's have a table of keywords, with the keyword and a count. Let's allocate enough space in the text array as are needed for the longest keyword (the longest is _Imaginary, which is 10 characters long). struct kw { char text[10]; int count; }; Now let's set up the table of keywords, with an initialized array. There are 37 keywords in the list, so.... struct kw keywords[37] = { { "auto" }, { "break" }, { "case" }, { "char" }, { "const" }, { "continue" }, { "default" }, { "do" }, { "double" }, { "else" }, { "enum" }, { "extern" }, { "float" }, { "for" }, { "goto" }, { "if" }, { "inline" }, { "int" }, { "long" }, { "register" }, { "restrict" }, { "return" }, { "short" }, { "signed" }, { "sizeof" }, { "static" }, { "struct" }, { "switch" }, { "typedef" }, { "union" }, { "unsigned" }, { "void" }, { "volatile" }, { "while" }, { "_Bool" }, { "_Complex" }, { "_Imaginary" } }; When something is partially, but only partially, initialized, the pieces not explicitly initialized are initialized to 0. Here, this not only affects the text[] arrays, as I mentioned above, but also the count members; in this particular case, this is convenient, because 0 is what we want them to start off as. Now, we'll need a loop, reading input characters, accumulating a possible keyword. We'll need enough space to hold the longest keyword. int main(void) { char id[10]; int c; while (1) { c = getchar(); if (c == EOF) break; ... } ... } (`id' being an abbreviation of `identifier'.) Oh, yes, we'll need a variable to record how long our potential keyword is, too. int idlen; We'd better start it off at zero. It's declared inside a function, so it starts off as trash. We have two choices here; we could initialize it as we declare it int idlen = 0; or we could initialize it with an assignment idlen = 0; I'm going to go with the second one, because (and this is a personal quirk of mine, not a C thing) I don't like initializations in in-function declarations. Now, for each input character, we need to decide whether it's an identifier character. This is a different test depending on whether it's the first character or not, because identifiers can't begin with digits, but they can contain digits in later characters. Let's push that out into its own function, to keep the main program's flow relatively clean. We could, instead, define "is a non-digit identifier character" and "is a digit" functions and use them; whether that's better is a judgement call. In this case, I think this way is better. We'll need to do one set of things if it is an identifier character, a different set if not, and we need to pass some indication of whether it's the first character down into the function. Alternatively, we could call a different function depending on whether it's the first character or not. Which way is better is a judgement call. In this case, the two functions would be so similar that I would prefer to use a single function with an argument indicating which way it should behave. In this case, I would do that by passing in the current length. (See below for more discussion of why.) while (1) { c = getchar(); if (c == EOF) break; if (identifier_character(c,idlen)) { ... } else { ... } } If it's not an identifier character, we don't have much to do - just reset the identifier length to zero so we know we're not reading an identifier: idlen = 0; If it is an identifier character, we need to record it, unless we're already at the max, in which case we can't record it because we don't have any space to do so in. But keep track of the length anyway, so we can tell the difference between the longest keyword and an identifier that begins with that character sequence but has additional characters after it: if (idlen < 10) id[idlen] = c; idlen ++; (the if statement here is an example of skipping the braces when the body is a single statement - in this case, an assignment expression turned into a statement by tacking on a semicolon). And...wait, something's wrong. We don't have anywhere we're checking to see whether our identifier is a keyword. Right, we need to do that when we see a non-identifier character at the end of an identifier. We need to add some code to the non-identifier-character case, to see if we've accumulated a possible keyword. Let's push that out into another function: if (idlen > 0) check_possible_keyword(); As a side note, this is as good a place as any to mention that there are at least three common ways to create function names built up from multiple words. We can use underscores, as in check_possible_keyword() above. We can use initial caps for each word, which in this case would give us CheckPossibleKeyword(). And we can use what's sometimes called camel case, or (self-descriptively) camelCase, which uses lowercases for the first word but initial-caps for each later word, which in this case would give us checkPossibleKeyword. Personally, I prefer underscores in most cases. End side note. So, main() now looks like int main(void) { char id[10]; int c; int idlen; while (1) { c = getchar(); if (c == EOF) break; if (identifier_character(c,idlen)) { if (idlen < 10) id[idlen] = c; idlen ++; } else { if (idlen > 0) check_possible_keyword(); idlen = 0; } } ... } Let's start writing those functions. identifier_character is probably simpler. There are various ways we can do this; let's use a switch. Recall that tests are actually ints, so let's make identifier_character return an int. If it's a letter or an underscore, it's always an identifier character, so return nonzero (traditionally, 1 is used when we want a nonzero value and don't care which nonzero value it is). If it's a digit, it's an identifier character exactly when the length-so-far (our second argument) is nonzero, that is, when it's not the first character. We could have passed in idlen>0 or some such instead of doing the test in identifier_character(). I have two reasons for doing it this way. One is that doing it this way means we have only one copy of the code for the test, instead of one copy per call site. (Since it's called from only one place in this program, this doesn't matter here, but it can for larger programs; as I mentioned above, it's good to get into good habits even when they "don't matter" because that makes it easier to exhibit them when they _do_ matter.) The other is that this way, we don't need to bother doing that test unless we care about the result. If it's anything else, it's not an identifier character, so return zero. This gives us: int identifier_character(char c, int len) { switch (c) { case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '_': return(1); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return(len!=0); break; } return(0); } There are two choices I've silently made here (though admittedly I'm not being silent about them now). One is to write those break statements. They are not necessary here, because the return statements before them ensure that the breaks will never be reached. Reflexively writing break statements is another of those good habits it's good to get into even when it "doesn't matter". The other is that I left off a default: case, instead writing the "other character" code as a return(0) after the switch. I could have, instead, written default: return(0); break; inside the switch. This is entirely a matter of taste. I have no particular preference either way on it; I've done it each way often enough. In this case, this way is two lines shorter; I'm close enough to evenly balanced otherwise for that to tip the balance for me. That leaves check_possible_keyword(). That needs to run through the list of keywords, checking whether it matches each one. This one doesn't need to return anything; if the identifier isn't a keyword, it shouldn't do anything, and if it is, it should increment the count in the keywords struct and still not return anything. We need a loop index to use when looping over the keywords, and let's write the loop skeleton: void check_possible_keyword(void) { int i; for (i=0;i<37;i++) { ... } } Now, how are we going to check whether our identifier matches a keyword? We'll need to check the characters, until we run out of characters in the identifier or we run out of characters in the keyword. We'll need a variable to keep track of where we are in that: int j; Now let's start writing that loop. We'll start at zero and loop until we run out of...oh, yes. If the identifier is too long, we don't need to do any checking; we have nothing to do in that case. We could do this in main, not even calling check_possible_keyword in this case, or we could do it here. Let's do it here: if (idlen > 10) return; Now let's start that loop. We'll loop until we run out of identifier characters (we hit idlen) or we run out of keyword characters (we hit the maximum length of 10, or we hit the 0 after the last character). We need to write the test as a "loop while this is true" condition. Start at the first character (0, because C arrays start at 0) for ( j = 0; and loop while we haven't run out of idlen AND (j < idlen) && we haven't hit the max identifier length AND (j < 10) && we haven't hit the terminating 0 (keywords[i].text[j] != 0); and, each time around the loop, advance to the next character j ++) { For each character, check whether it matches. If it matches, do nothing here; all we want to do is go to the next character, which is already covered in the for(;;). If it doesn't match, go on to the next keyword. This would be a continue statement, except we're in a nested loop. We could use a goto, we could add a variable to record whether we're breaking out here, or we could just break out and then, after the loop, test to see whether we broke out because we ran out or because we had a mismatch. (There's a subtlety lurking here; discussing it here would take me too far field, so I'll talk about it below, after getting this working.) In this case, we'll need to do some checking after the loop in any case, because we want to count a match only if we run out of idlen at the same time as we run out of keyword text. So let's just break and then check, after the loop, to see what happened. if (keywords[i].text[j] != id[j]) break; That's it for the inner loop. } Now, let's see why we left the loop. If we've hit idlen at the same time as we've hit the end of the keyword (max length, or the terminating zero), we have a match. Anything else isn't a match: either we ran out of one string before the other or we had a character mismatch. If it's a match, increment the count and return; otherwise, go on to the next keyword, if any. So, if we hit idlen AND if ( (j == idlen) && we hit the end of the keyword ((j == 10) || (keywords[i].text[j] == 0)) ) { then we have a match! Count it and we're done for this call. keywords[i].count ++; return; } No match. Go on to the next keyword. This just means the end of the loop on i. If this loop terminates normally, it means we scanned all keywords with no match; in this case, we don't want to do anything, so just fall through to the end of the function. So, check_possible_keyword looks like void check_possible_keyword(void) { int i; int j; if (idlen > 10) return; for (i=0;i<37;i++) { for (j=0;(j 10) return; for (i=0;i<37;i++) { for (j=0;(j 0) check_possible_keyword(); idlen = 0; } } for (i=0;i<37;i++) { printf("%d ",keywords[i].count); for (j=0;(j<10)&&(keywords[i].text[j]!=0);j++) { putchar(keywords[i].text[j]); } putchar('\n'); } return(0); } I've deliberately made some mistakes here. They are mistakes that are easy to make when you're constructing a program like this, which is why I left them in; sooner or later you'll make mistakes like them, and I want to help you learn to recognize and repair them. Exactly how these mistakes manifest will depend on your system and your compiler. With the compiler I have on the system I'm writing this on, with the source in keywords.c, I get keywords.c: In function 'check_possible_keyword': "keywords.c", line 78: error: 'idlen' undeclared (first use in this function) "keywords.c", line 81: error: 'id' undeclared (first use in this function) keywords.c: In function 'main': "keywords.c", line 103: error: 'EOF' undeclared (first use in this function) "keywords.c", line 115: warning: incompatible implicit declaration of built-in function 'printf' The first two are pointing out to me that id[] and idlen are declared inside main(), but I'm trying to use them in check_possible_keyword(). As I remarked back when I was first introducing functions, for a variable to be visible in multiple functions, you need to move its declaration outside, and before, all the relevant functions. So let's move the declarations of id and idlen outside main(), up to just after the keywords[] array initializer. On retrying the compile, I find that makes those two errors go away, but the others - the ones complaining about EOF and printf - are still there. (When a test compile is cheap enough, it's often a good idea to fix only the first error or two, because certain errors can lead to a cascade of other messages. With experience, you'll get better at recognizing when certain errors will cascade and when not; for the moment, I'm trying to treat this as if I didn't have that experience.) The fix this time is simple: I forgot to #include . I added that at the top of the file, and now it compiles fine. When you run this, you will normally want to feed it input from a file somewhere, something like a program source file or a file holding this course or some such. Exactly how you do that is system-dependent. If you're on a Unix variant, like Linux, you would do something like $ ./keyword < keyword.c (assuming the program is called keyword and its source is keyword.c) or $ ./keyword < hello.c (again, assuming it's called keyword, and you have a file called hello.c you want to feed it as input). You should get something like this out: 1 auto 5 break 64 case 4 char 1 const 1 continue 1 default 1 do 1 double 2 else 1 enum 1 extern 1 float 5 for 1 goto 8 if 1 inline 11 int 1 long 1 register 1 restrict 7 return 1 short 1 signed 1 sizeof 1 static 3 struct 2 switch 1 typedef 1 union 1 unsigned 4 void 1 volatile 2 while 1 _Bool 1 _Complex 1 _Imaginary Those are what I get when I feed it its own source code as input. Since each keyword occurs once in the keywords[] initialization, every number is at least 1. If I feed it hello.c, containing our very first example program, I get 0 auto 0 break 0 case 0 char 0 const 0 continue 0 default 0 do 0 double 0 else 0 enum 0 extern 0 float 0 for 0 goto 0 if 0 inline 1 int 0 long 0 register 0 restrict 1 return 0 short 0 signed 0 sizeof 0 static 0 struct 0 switch 0 typedef 0 union 0 unsigned 1 void 0 volatile 0 while 0 _Bool 0 _Complex 0 _Imaginary This program works. But it could be improved. First, though, I want to talk about that subtlety I mentioned, above, when I was first writing the inner for loop in check_possible_keyword. The test in that loop is (j=10, even if the actual value is trash, then, in this particular case, this matters only for efficiency. But that's not promised, and, while in this particular case it's unlikely to fail in practice, it might, and there certainly are other cases where it matters a lot more. Back to the program. As I said, it can still be improved in various ways. Let's start with one of the simplest and most superficial: when printing the output, we ran into a problem printing the keyword string. printf does indeed have the ability to handle that, but I didn't include it because I hadn't explained how it worked yet. Remember how you print integers with %d and floats with %g? Those are termed `format's. There are a bunch more formats. One - %s - is for printing strings. But there are two catches. The first is how to use it. %s expects a pointer-to-char in the printf argument list; it prints the characters it points to, until it finds a 0 char (it doesn't print the 0 char; that just terminates the print operation). This is fine for most of our keywords, but the longest one doesn't have a 0 terminating it. For reasons I'll go into later, it would probably work anyway in this particular case, but for the wrong reasons. There are two correct fixes for this. One of them is to tell printf that it should print a string, but in no case more than 10 characters. You'd do this by using %.10s instead of just %s as the format spec. The other is to enlarge the text[] array in each struct kw by one char, so that each keyword, even the longest, has a terminating 0 char. If we do this, we can also clean up the code a little by eliminating the tests against 10 to see if we've reached the end of a keyword. We can then use plain %s when printing keywords. Personally, I prefer this approach. The extra memory - one extra char for each keyword - is, in my opinion, far outweighed by the code simplification, especially since, for reasons I haven't yet discussed, there usually will not actually be any extra memory cost. What do we pass to printf for the %s (or %.10s, if you go that way) format? Well, recall that I said printf prints chars starting with the pointed-to one, until it finds a 0. So we want to pass it a pointer to the first char in the keyword name: &keywords[i].text[0]. So, now, our source code looks like this: #include struct kw { char text[11]; int count; }; struct kw keywords[37] = { { "auto" }, { "break" }, { "case" }, { "char" }, { "const" }, { "continue" }, { "default" }, { "do" }, { "double" }, { "else" }, { "enum" }, { "extern" }, { "float" }, { "for" }, { "goto" }, { "if" }, { "inline" }, { "int" }, { "long" }, { "register" }, { "restrict" }, { "return" }, { "short" }, { "signed" }, { "sizeof" }, { "static" }, { "struct" }, { "switch" }, { "typedef" }, { "union" }, { "unsigned" }, { "void" }, { "volatile" }, { "while" }, { "_Bool" }, { "_Complex" }, { "_Imaginary" } }; char id[10]; int idlen; int identifier_character(char c, int len) { switch (c) { case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '_': return(1); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return(len!=0); break; } return(0); } void check_possible_keyword(void) { int i; int j; if (idlen > 10) return; for (i=0;i<37;i++) { for (j=0;(j 0) check_possible_keyword(); idlen = 0; } } for (i=0;i<37;i++) { printf("%d %s\n",keywords[i].count,&keywords[i].text[0]); } return(0); } This is getting close to the form I would actually have written it in. There are only a few other things I'd change. One is that it's got 10 and 37 scattered around. If we change the list of keywords, so the longest keyword is no longer ten characters long, we need to change three places where 10 occurs and one where 11 occurs (the 11 because it's 10 plus one extra). And 37 is the number of keywords; if we add or remove keywords, we need to change three places where 37 occurs. (We actually don't need to change the 10s and the 11 unless we introduce a _longer_ keyword; having extra space in the text[] arrays may be wasteful, but it's not incorrect.) We'd really like to be able to make those some kind of identifer, so we don't have to repeat the magic numbers all over the place. We could declare variables to hold them, but there's a problem. That works fine for two of the occurrences of each magic number, but there's one 37, one 10, and that 11, which are in constructs where the language doesn't allow us to use variables' values. Array sizes have to be compile-time constant expressions, and variables' values do not count. (Simplification: in some cases array sizes do not need to be compile-time constant expressions - but, even then, none of the three arrays in question here (id[], keywords[], and text[] inside struct kw) are any of those cases.) C is nice enough to provide a facility we can use for this, though. It's part of the preprocessor, something we've been using in a form all along, but haven't really talked about. So far all we've seen of it is those #include lines; there's one other preprocessor thing we've used (EOF), but we haven't really recognized it as having anything to do with the preprocessor. It's called a preprocessor because it performs some relatively simple textual processing on the source code before the compiler proper gets hold of it. Or, at least, that's how it works conceptually; in most implementations, preprocessing and compilation are performed in a relatively tightly coupled way. I'm not going to throw the full complexity of the preprocessor at you now. It's got some relatively elaborate aspects which we do not have occasion to care about yet. For the moment, all we need is a very simple feature, something often called `manifest constants'. In this case, we might add two lines #define MAXKEYLEN 10 #define NKEYWORDS 37 and then change 10 to MAXKEYLEN, change 11 to MAXKEYLEN+1, and change 37 to NKEYWORDS. Then, if we want to change any of those, we just need to change it one place - the #define line - and all the uses of it will change to match. Because preprocessing happens before compilation proper, things like char id[MAXKEYLEN]; get turned into char id[10]; before the compiler proper gets to see them, so they are still compile-time constant expressions and everything still works. MAXKEYLEN and NKEYWORDS fit the syntax of identifiers, it's true, but they are not variables; if we write MAXKEYLEN = 44; then the compiler will see 10 = 44; and, correctly, complain that the thing on the left-hand side is not an lvalue - it cannot be assigned to. (It _is_ possible to change what MAXKEYLEN expands to. But it's not done with an assignment statement, and even if we do do it, that doesn't make it a variable in the sense we've been using. If we do that change, the change is not something that gets executed in program-flow order; rather, it affects all uses of MAXKEYLEN from the point of the change to the end of the file, regardless of what order code gets executed in. The preprocessor is a textual-substitution pass over the source code before compilation proper happens.) Incidentally, it is not coincidence that I used ALL CAPS for MAXKEYLEN and NKEYWORDS. It is traditional to do that for #define definitions, especially manifest constants such as we're using here. Doing otherwise is not wrong from a C-language point of view, but it is very wrong from a human-understanding point of view. There are other classes of things that are commonly written in ALL CAPS, but not very many of them. As I said, there are a few more small tweaks to this program to turn it into the form I'd actually write. But I'll leave them until I discuss the relevant language features. ---------------- It's time to de-simplify another simplification from above. You may recall that I said variable declarations can be written at the top of a function body or outside all functions. Later, I outlined various other things you can use braces for, one of which I (even later) explained was really compound statements; a function body is an example of a compound statement. You actually can write variable declarations at the top of any compound statement. It's entirely fine to write things like if (j < 0) { int k; for (k=0;k<64;k++) { ... } } I mention this now because I want to explain declaration scope. Every identifier has what's called a scope (not just variable names; this also applies to other uses of identifiers, such as struct tags and goto labels). Some uses have file scope, some have function scope, some have block scope. (Simplification: there is a fourth possible scope, prototype scope, which it would take us too far afield to talk about now.) The scope of a thing is the (textual) range during which the use of that identifier in that way refers to that thing. For example, in our last keyword-counting program, above, there is a declaration "int j;" in check_possible_keyword(). That declares j as a variable, having type int. The scope of that declaration is, by definition, the parts of the source file during which use of the identifier j as a variable refers to that particular variable. In this case, that's most of the body of check_possible_keyword - from the end of the declaration to the end of the body. Outside that scope, the use of j as a variable either is an error or refers to some other variable. (In this case, an error. But if we were to use i instead of j, then the same remarks about check_possible_keyword apply, but there is a different range - most of the body of main() - where use of the name i as a variable is not an error, but refers to a different variable.) File scope is the scope that results from a declaration which occurs outside all functions. The potential scope runs from the point of declaration to the end of the source file. Block scope is the scope that results from a declaration which occurs inside a compound statement. The potential scope runs from the point of declaration to the end of the enclosing { } block. You may note I've spoken of "potential" scope. This is because scope can have holes punched in it. Consider this: int fn1(void) { int foo; ...code X... if (...code T...) { float foo; ...code Y... } ...code Z... } In this case, the scope of the "int foo;" declaration is not from the point of its declaration to the end of the containing block. It does not include code Y, because the declaration "float foo;" creates a different variable and causes the name foo to be associated with that second variable throughout code Y. The outer foo, the int one, is visible only in code X, code T, and code Z. Using the name foo as a variable in code Y refers to the inner, float, foo - the float declaration is said to shadow, or hide, the int declaration. Function scope is a bit different. Only one kind of identifier has function scope, and that is a label, a label of the sort used by goto. The scope of such a label is the entire containing function, both before and after the label itself. Thus, when we moved id[] and idlen from main() to outside all functions, in the keyword-counting program, the point was to make their scope include both main() and check_possible_keyword. This is mostly a terminological aside. But it is closely related to another property every object has, one that's going to be important soon. That is the portion of the program during which that object exists, but in this case we're concerned not with textual ranges but with chronological ranges. The term for this is the object's `storage duration'. There are three possible storage durations: static, automatic, and allocated; we've already met the first two of those, and I alluded briefly to the third when I wrote of C having calls to allocate and free memory. If an object has static storage duration, it exists from the moment the program starts to the time it finally exits. If the object has an initializer, it is initialized only once, prior to program startup; if not, it is initialized to zero. (How such initializations happen can vary from system to system. Sometimes they are part of the executable file; sometimes they happen as part of the startup code, before main() gets called.) The only objects with static storage duration we've met so far have been ones declared outside functions, at file scope. Any object declared at file scope has static storage duration. It is also possible for an object declared at block scope to have static storage duration, but we haven't yet seen how. If an object has automatic storage duration, its declaration must have block scope, and the object exists from the moment that block begins execution to the moment that block ends execution. (Entering a nested block, or calling out to another function, suspends but does not end execution of the containing block.) (Simplification: there is one condition, one we haven't seen yet, where an object with automatic storage duration exists for a potentially even shorter period.) If an object with automatic storage duration has an initializer, initialization is performed each time the associated block begins execution. Each instance of execution of the block gets its own distinct object; this includes recursive executions of the block (we haven't yet discussed recursion). I mentioned, above, that one effect of moving a declaration from (what I can now call) block scope to file scope is that it gets initialized to zero if there's no explicit initializer. As I remarked in a simplification aside, this is actually an effect of storage duration, not scope; it's just that, in what we've seen so far, the two have correlated perfectly. But, once we learn how to give an object declared at block scope static storage duration, that will no longer be true. Let's learn that now. You do this by writing the declaration just as if it were for an ordinary block-scope object with automatic storage duration, only you tack the keyword `static' on in front: if (...) { static int first_time = 1; if (first_time) { // Executed first time through only ... first_time = 0; } ... } In this example, first_time has block scope - the name "first_time" doesn't refer to that variable outside the block that contains it - but static storage duration. Only one instance of the variable exists (per program run, of course), and it is initialized only once, prior to program startup. Confusingly, you can also use the `static' keyword on declarations with file scope, but it means something else there, because all such declarations must have static storage duration. At file scope, `static' doesn't affect storage duration (which is always static) but linkage, an attribute of identifiers we haven't discussed yet, and won't for a while. I mention it now not because I'm going to describe it now, but in an attempt to reduce confusion - now that I've mentioned the static keyword, I want to warn you that it doesn't always have the meaning I've just described for it. (I think it would have been a better choice to use a different keyword for the linkage effect, since the meaning is different.) ---------------- No, what I'm going to describe next is the third storage duration. We've seen static storage duration and automatic storage duration. The third possibility is allocated - also called dynamic - storage duration. Objects with allocated storage duration cannot be referred to with variables; there is no way to declare a variable such that its use refers to an object with allocated storage duration. The only way to use such objects is via pointers. (With the preprocessor, we can write code that looks as though there's a variable referring to an object with allocated storage duration, but that's not actually what's going on in that case.) For this, we need a tiny digression, describing one more operator. On the list of operators above, there are two that I haven't yet described. One of them is `sizeof'. That's the one that's relevant here. The sizeof operator is a prefix operator, that is, it appears before its operand. Its operand can be an expression or it can be a type name with parens around it. (You'll often see parens around the operand to sizeof even when it's an expression - a bit like return in that regard, in that some people tend to treat it syntactically somewhat like a function.) When used on an expression, sizeof returns the size of that expression's value; when used on a type, sizeof returns the size that an instance of that type would have. (In the expression case, the expression is not actually evaluated.) So what's a thing's size? Well, in C, every type is, or at least can be, stored in memory. When this is done, it occupies a certain amount of memory. That is its size. It is measured in chars - sizeof(char) is always 1, for example. (Simplification: there is a kind of lvalue, bitfield, that does not have a size in this sense, and function types, which I haven't yet talked about, also don't have sizes this way. sizeof() isn't applicable to such types - though some compilers support sizeof() on such types as an extension.) Why do we need sizeof() to explain allocated storage duration? Because, in order to allocate storage for an object, we have to know how much storage to allocate. In a very few cases (chars, mostly) we don't need sizeof for this, but in most cases we do. Let's go back and grab a struct from an example, above: struct transform { float xx, xy, x; float yx, yy, y; }; Suppose we want to allocate one of them dynamically. (We'll see, later, why we might want to do that.) We'd do that by declaring a pointer to one. This can be a simple variable, a struct element, an array element, whatever. For expository ease, I'll make it a simple variable for now: struct transform *t; What next? We allocate a struct transform. When we do this, we get back a pointer to it. We then store that pointer into t, and we can use t->xx and t->y and such just as if t were a pointer created with unary &. The actual allocation happens with malloc(). (Simplification: there are actually three routines we can use, plus possibly others depending on the operating system in use; malloc() is just the simplest, and perhaps commonest, of them.) malloc() has to know how much space to allocate, though. We tell it that by passing an argument. That's where sizeof comes in. Here's what it looks like: t = malloc(sizeof(struct transform)); or t = malloc(sizeof(*t)); Which is better? That's a matter of taste, and what kind of changes you expect might need to be made. If you change the type of t, the first call still allocates space for one struct transform, whereas the second allocates enough space for one of whatever type t points to. This sounds as though the second alternative is better - and, sometimes, it is. But someone reading the code has to go back and check what kind of object t is declared as pointing to to see what's being allocated in the second form, whereas the first form says right there what kind of object is being allocated. (Like many library routines, malloc() and related routines are declared in an #include file. In the case of the malloc() family, the appropriate line is "#include ".) In some cases, malloc() is all you need. If you're allocating something that needs to persist until your program finishes, it's fine. But if you need to allocate it, use it, and then let it go again, which lots of programs do, then you need to be able to de-allocate the memory as well. This is done with free: free(t); Notice that you don't need to pass the amount of space you allocated when you free it. As this implies, you can't free only part of an allocated block - freeing it is all or nothing. This isn't terribly relevant if you're allocating objects one at a time, as above, but you can also allocate multiple objects at once: t = malloc(4*sizeof(struct transform)); This allocates space for 4 structs transform, returning a pointer to the first of them. You may recall, back when I was talking about arrays, that I said that pointer arithmetic moves in units of pointed-to objects, and that subscripting with [] actually did that. Now, we can see how that becomes useful. If we do the above, then, yes, we can work with t->xx and t->y and such, but doing that accesses only the first of the four structs transform we allocated. One thing we can do to access the others is to add an integer to t to get a pointer to one of the others struct transform *t2; t2 = t + 3; ...use t2->yx and the like... In this example, t2 points three structs transform further along than t does, so it points to the last of the four we allocated. But subscripting also works that way. We can write t[0].xy, t[1].y, and such, to access the other structs transform. As usual for subscripting, the subscript does not have to be constant; we can use any integer-valued expression as a subscript: do_something(t[i-j+1].y,j); t[i] is actually just another way of writing *((t)+(i)). This is true even for arrays, and is why using an array in a value context converts it to a pointer to its [0] element: so [] has a consistent interpretation. It also means that you can just mention an array to get a pointer to its [0] element. Recall, above, that when printing out keyword names, I wrote &keywords[i].text[0] to get a pointer to the beginning of the text string? I could equally well have written just keywords[i].text to get the same thing, because the array `value' turns into a pointer to its [0] element. (I would usually write the &...[0] form anyway, but that's another personal indiosyncracy of mine. But this is uncommon; you will more often see the version without the &...[0] around it.) This also leads to an (in)famous quirk of C: because a[b] is equivalent to *((a)+(b)), and addition is symmetric, that's equivalent to *((b)+(a)), which is in turn equivalent to b[a]. Thus, [] is actually symmetric; you can write x[t] and it means the same thing as t[x]. (Taking advantage of this is (almost always) a bad idea, because it (almost always) makes the resulting code harder to read.) But we now have the tools to understand another change I'd make to the keyword-counting program: I'd turn char text[11]; into char *text; (Well, actually, I'd turn it into "const char *text;", but I haven't explained const yet.) This would not necessitate any changes to the keywords[] initialization, because the string literals - "auto", "break", etc - are initializing pointers, which is a value context, so, being arrays, they turn into pointers to their first elements, which is exactly what we want. Whether this saves memory depends mostly on the architecture: it saves all the bytes after the terminating 0 in each string, but costs the storage for the pointers. Which is larger depends on the architecture. It does not mean we have to change the code, because all the references to text treat it like an array and hence work equally well with a pointer. It does lead to (very slightly) slower code, because accesses to the text strings have to follow the pointer - but it can also lead to (very slightly) faster code, because keywords[] is smaller and thus will fit into fewer cache lines. (Though, admittedly, most caches these days are far bigger than this whole program.) If I explain one more minor thing about arrays, we can combine that with something I introduced above and get one more tweak I'd make to that program. When initializing an array, you can leave off the array dimension, and the compiler will compute the array size for you - it will be just large enough to hold all the initializers. Thus, we can write int vec[] = { 1, 1, 2, 3, 5, 8 }; and it's exactly as though we had written int vec[6] = { 1, 1, 2, 3, 5, 8 }; And if we write char map[] = { ['a'] = 'A', ['b'] = 'B' }; then map[] will be just big enough to include both 'a' and 'b' as subscripts (whichever is larger). (If there are any other elements, which there will be in this case because, if nothing else, neither 'a' nor 'b' is zero, they are initialized implicitly same as any not-explicitly-initialized elements of an initialized array: to 0.) If you do this when initializing an array-of-char with a string literal, the compiler-chosen size will include space for the trailing '\0'. This sounds a bit dangerous, because we then don't know how big map[] is, so we don't know how big a subscript we can use. This is where we combine it with something else I mentioned above: sizeof. One of the few contexts in which an array does _not_ convert to a pointer to its first element is when it's the operand to sizeof. sizeof(map) will be the amount of space occupied by map, not the amount of space occupied by a pointer to map[0]. If we want to know how many elements map contains, we can write sizeof(map) / sizeof(map[0]) and we'll get the number we need. (In the particular case of map, above, we don't need the "/ sizeof(map[0])" part, because map is an array of chars, and sizeof(char) is always 1. But if it were an array of anything else, we'd need that, and it doesn't hurt anything even in the array-of-chars case.) The tweak I'd make to the keyword-counting program, then, is to leave off the 37 in the declaration of keywords[], and #define NKEYWORDS not as 37 but instead as (sizeof(keywords)/sizeof(keywords[0])). Then I can add or remove a keyword and I don't need to touch anything else for the program to adjust to match. There is a minor point lurking here. I would write #define NKEYWORDS (sizeof(keywords)/sizeof(keywords[0])) rather than #define NKEYWORDS sizeof(keywords)/sizeof(keywords[0]) In this particular case, it makes little difference in practice. But, in general, when defining a macro whose expansion is an expression, it's usually a good idea to put parentheses around the expansion. The reason it makes little difference in this case is that division is one of the highest-precedence arithmetic operators, so there is little opportunity for trouble. But if the arithmetic were something else, like, say, addition #define SIZE sizeof(thing) + sizeof(otherthing) then we'd have trouble if we tried to do something like n = 2 * SIZE; beacuse this would expand to n = 2 * sizeof(thing) + sizeof(otherthing); and the 2 would multiply only the first sizeof(), which is probably not what was intended and certainly is not what "2 * SIZE" appears to be trying to do. Similarly, if we wrote something like m = max - SIZE; then we'd get m = max - sizeof(thing) + sizeof(otherthing); which is probably not what was intended and definitely not what the original "max - SIZE" appears to be doing. Thus, I'd use parens even for NKEYWORDS, where it's using division, less because they're actually necessary in that case and more because it's a good habit to get into. (There are a few operators that can cause precedence lossage even with division, such as ~, but none of them make semantic sense when applied to NKEYWORDS. That's why I say it makes little difference in practice.) The next tweak I'd make is one we already have the tools to understand. I would convert id from an array to a pointer, and, at startup, run through the list of keywords once, determining the longest one and allocating that much space and making id point to it. I'd save the maximum length in a variable and use that elsewhere in the code where 10 is currently used. It would cost a tiny bit of run time, but for most use cases that cost is less than the potential cost of adding a new keyword longer than the current maximum and having the rest of the program explode as a result. This brings me to something I never mentioned when talking about arrays, above. In C, subscripting never checks whether the subscript is in range. In part this is because, as an OS implementation language, you sometimes _want_ to access outside of the declared bounds of an array. Such code is nonportable and machine-specific, but in a few places in an OS implementation that's appropriate. In part it's also because subscripting also works with pointers, and it's significantly harder for the compiler to check whether the subscript is in range for a pointer. (Admittedly, each of those could be worked around. But it's not the C way to try to protect programmers from themselves, at least not when doing so would have run-time costs, and doing that sort of thing for subscripting pointers would have to incur run-time costs.) There are two more things I should mention before I leave pointers for other topics. One is void pointers; the other is nil pointers. (Nil pointers are what most other sources call "null pointers", or sometimes even "NULL pointers"; I'll explain, below, why I use a different term.) First, void pointers. I mentioned that you can declare a pointer to any other type. That includes void. But what does a pointer to void even mean? In the usual interpretation, it doesn't mean anything, because there are no values of type void, so there can never be anything for such a pointer to point to. You can't declare a variable of type void, for example. So, the C standard has turned pointer-to-void into a special case. It is a generic pointer type, used when you want to work with a pointer to some type without needing to know exactly what that type is. An example of such a case is malloc(): it needs to return a pointer without knowing what type it will be used to point to, which is just the kind of case void * is for, and, indeed, malloc() returns void *. Similarly, free()'s parameter has type void *. A pointer to any other type (simplification: actually, to any other object type) can be converted to void * and back without loss of information - that is, the pointer you get after converting to void * and back again is "the same as" - equal to, operationally indistinguishable from - the original pointer. Furthermore, by special fiat, you can convert between void * and any pointer(-to-object) type without, in most cases, needing even a cast. For example, because, as mentioned above, malloc returns void *, you can write t = malloc(sizeof(struct transform)); instead of needing t = (struct transform *) malloc(sizeof(struct transform)); Second, nil pointers. Each pointer type has one distinguished value which never points to anything. This is what I'm calling a nil pointer (of that type). The C standard calls this a null pointer; I don't like that terminology because it leads to confusion. Explaining this requires me to describe two more small things from the C spec. One is that there is a thing called a "null pointer constant". In the words of the C99 spec, "[a]n integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant". The other is related: some header files are specified to define NULL as a preprocessor macro which expands to a null pointer constant. Many people use NULL when setting a pointer to nil, or when passing a nil pointer to a function. So, what's the problem? The problem is that this use of NULL leads people to think that it's suitable for use as a nil pointer in all possible contexts, which it isn't. There are a few contexts (involving language features I haven't yet discussed) where a pointer of a particular type is required, but there isn't enough context for the compiler to know what type that is. In these contexts, NULL is not correct, because it carries no type information, and a nil pointer of one type is not the same thing as a nil pointer of another type (and neither one is the same as integer zero, and integer zero is one of the specifically permitted definitions of NULL). The problem is that, on many machines, nil pointers of different types, or nil pointers and integer zeros, are similar enough that such code appears to work. This leads sloppy coders to think that it's correct, writing such code freely, then getting bitten by the resulting portability problems upon trying to use the resulting code on a machine where there *is* a difference. They typically then incorrectly blame the new system, rather than their incorrect code which the former system(s) happened to let them get away with. Of course, NULL is fine in such contexts with an appropriate cast. But so is 0, or any other null pointer constant. Compounding this is the NUL name for ASCII codepoint 0. I have seen at least a few cases of people writing NULL when they mean "char with value 0", either ASCII codepoint 0 or a C string-terminating 0. I conjecture that this derives from the similarity of NULL to NUL. And NULL may have pointer type ("or such an expression cast to type void *"), and there is no promise that converting _any_ pointer, even a nil pointer, back to an integer will yield zero. And, again, machines where the code happens to result in the intended behaviour are common enough that sloppy coders come to think it's correct, because it works on their development and test systems. So, my stance is that the only value NULL holds is the implied comment value that the coder thought of it as a pointer. As the confusion with NUL indicates, even this implication is discouragingly weak. So I consider the confusion it engenders to outweigh the benefit it brings and I recommend that it never be used. I mention it here largely because you will eventually happen across it, and you should know not only what it actually means, but what it is often misused to mean. This is why I call the distinguished value a "nil pointer": to distance it somewhat, linguistically, from the morass of confusion that surrounds NULL. One other special promise applies to null pointer constants: they can be compared to any pointer type, or converted to any pointer type, without needing a cast. You can write int *ip; ip = 0; or char *s; if (s == 0) without needing to cast the 0, because it's a null pointer constant. One other note in passing: I mentioned, above, that a pointer to an object is sometimes confused with its address, simply because memory addresses are by far the predominant implementation of pointers. Many machines, most common machines at this writing (2021), implement pointers as simple memory addresses, with nil pointers being the all-bits-zero address (which most operating systems take care to ensure is invalid). This produces a perception that all pointers are just memory addresses, leading people to take a pointer of one type and treat it (typically via pointers-to-pointers) as if it were a pointer to some other type. While this is occasionally a reasonable thing to do (in machine-dependent code), far too many people slide into the mindset that it is a correct and portable thing to do, which it is not. (For example, on some, relatively unusual, machines, the memory addressing unit is fairly large, such as 32 bits, while chars are smaller than that. Typically, C implementations on such machines implement a pointer-to-char as a memory address plus some additional char-within-word information, not the same thing as a pointer to a larger type such as int. This is entirely permitted by the C spec.) On a related note, there are library routines (which we haven't seen yet) which clear memory to all-bits-zero. This is a perfectly reasonable thing to do in some cases, such as when the memory in question holds integer types; C specifies enough about the representation of integer types that all-0-bits must represent the value zero for any integer type. But there are no such promises for floating-point types or pointer types. However, on most current machines, floating-point zero and nil pointer values _are_ represented with all-bits-zero, so people tend to go ahead and use the all-zero-bits library routines even when the memory they're zeroing is then used to hold floating-point or pointer values; I've even seen this done in documentation examples for (supposedly-)portable operating systems. Keep the conceptual notions of "nil pointer" or "zero floating point value" rigidly separate from the implementation notion of "all bits zero", and don't let yourself slide into thinking of all pointers as being the same under the hood, and you should be able to avoid the worst of these problems. If you can, find a machine - or an emulator for a machine - of one of the exceptional types, like a word-addressed machine or a 36-bit machine, and work there as well as on more conventional machines; the mental habits you will perforce form will lead you to produce better code. There are other pointer topics I want to talk about - pointers to pointers, pointers to functions, and pointers to arrays - but I'm going to defer them a little bit. One more tweak I'd make to the keyword-counting program that I don't need to explain anything more for is that when comparing a keyword text character with 0, I'd write the 0 as '\0' instead of 0. This makes no difference to the semantics, because '\0' is, by definition of the language, 0, but it does indicate to humans reading the code that the intent of '\0' is that it be a char rather than a generic int. (At least one compiler I've seen is capable of producing warnings based on the use of '\0' instead of 0.) ---------------- And, with that, let's head off into a different part of the language. Let's look at two more aspects of functions. First, we'll need a bit of a technicality: forward declarations. I haven't really strictly separated declarations from definitions, so far. But we're coming up on something for which the distinction makes a difference. A _definition_ of a thing provides the full details about the thing - variable, function, whatever. A pure _declaration_ of a thing, in contrast, just provides enough information for other code to use the thing: read and write the variable, call the function, whatever. We've been able to get away with glossing over the difference because we've been working with programs that (a) are entirely in one file and (b) never want to use anything before it's defined. Even with that, though, we've had a few allusions to the distinction when talking about the way #include lines declare (not define) things like srandom() or printf(). (Also, definitions of things also operate as declarations for those things, confounding the terminology somewhat.) For example, if we write int idlen; at file scope, that is a declaration that can also be a definition. This is true of any variable declaration with no initializer at file scope (simplification: provided it isn't marked extern, something we haven't yet seen). But it isn't necessarily a definition. If we write int kind; and also (either before or after) int kind = 4; then we have two declarations, one of which has to be a definition (any variable declaration with an initializer is a definition). The other one is just a declaration. You can think of a declaration with no initializer as a tentative definition: if we have one or more such, and no unequivocal definition, the tentative definitions are merged into a definition. But, if there is a non-tentative definition, then the tentative definitions are treated as just declarations. (Simplification: this is true only within a single file, or on certain systems; see below, where I talk about separate compilation and linking.) Thus, we can write int kind; int kind = 4; or int kind = 4; int kind; and it amounts to the same thing as just int kind = 4; You may wonder what the point of this is. With just ints, like this, there isn't really much point, at least not until we start to deal with the preprocessor and separate compilation. But consider this: struct cell { struct cell *links[4]; // up, down, left, right int data; }; Now, if we want to set up a little loop of four of the structs cell, forming a square, we'd like to do something like this (I'm ignoring the data element here, since it's the links that motivate what I'm writing about): struct cell cell_ul = { { 0, &cell_ll, 0, &cell_ur } }; struct cell cell_ur = { { 0, &cell_lr, &cell_ul, 0 } }; struct cell cell_ll = { { &cell_ul, 0, 0, &cell_lr } }; struct cell cell_lr = { { &cell_ur, 0, &cell_ll, 0 } }; But, if we try to compile that, we'll get complaints. Here's what I get when I try it: "cells.c", line 7: error: 'cell_ll' undeclared here (not in a function) "cells.c", line 7: error: 'cell_ur' undeclared here (not in a function) "cells.c", line 8: error: 'cell_lr' undeclared here (not in a function) Which makes sense: those are the places where I'm trying to refer to a struct cell that hasn't been defined yet at that point. So, what we need to do is add declarations that aren't definitions before we refer to them: struct cell cell_ul; struct cell cell_ur; struct cell cell_ll; struct cell cell_lr; struct cell cell_ul = { { 0, &cell_ll, 0, &cell_ur } }; struct cell cell_ur = { { 0, &cell_lr, &cell_ul, 0 } }; struct cell cell_ll = { { &cell_ul, 0, 0, &cell_lr } }; struct cell cell_lr = { { &cell_ur, 0, &cell_ll, 0 } }; The first four declarations are what are sometimes called forward declarations. (We don't actually need all four of them, only the three that are used before they are defined - the three the compiler errors I quoted above talk about. I would tend to declare all four of them anyway.) The reason this is relevant is that the same thing can be done for functions. We haven't yet learnt how to declare a function without defining it, but that's the only piece we're missing. (We haven't learnt it yet beacuse we haven't needed it yet.) The simplest way to do that is to just replace the function body with a semicolon. For example, if we have float square(float v) { return v*v; } then the simple way to declare-but-not-define that function is to just replace the function body with a semicolon: float square(float v) ; or, as it would more usually be written, float square(float v); It is permitted to skip the formal argument variable names when doing this, as in float square(float); and in some respects it's better to do so, but it's not required, and, for our purposes, most of the reasons why it's often a good idea to remove the formal argument names don't apply. (I'll talk about those reasons, below, when discussing separate compilation.) Why would you want to declare a function without defining it? The commonest reason, in practice, is that the function is defined off in a different file. That doesn't apply here; the reason we care about is that it's how you call a function before it's defined. In most cases, you can move functions around so that each function is defined before any of the calls to it. But this isn't always possible, and what I want to explain next is one of the cases where it's not. It's time to talk about recursion. At its simplest, recursion is a function calling itself. The classic example (which is not a particularly _good_ example) is the factorial function, which, in its recursive formulation in C, looks something like this: int factorial(int n) { if (n < 2) return(1); return(n*factorial(n-1)); } It borders on trivial to convert this into a loop instead (which is why it's not a particularly good example), but it does serve as an example of a function calling itself recursively. A more plausible example might be struct node { struct node *left; struct node *right; ...maybe other elements... }; int count_nodes(struct node *n) { if (! n) return(0); return(1+count_nodes(n->left)+count_nodes(n->right)); } The first line of count_nodes's body is something we haven't seen before, but it follows logically from things we have seen: we are treating n, which is a pointer, as a test, negating it with !. !x is equivalent to x==0; for pointers, the 0 is a null pointer constant and gets silently converted to a nil pointer of the type appropriate for x. Thus, "if (! n)" here is "if n is a nil pointer". The structs node here form a binary tree, with left and right child pointers, and count_nodes returns the number of nodes in a tree. A nil pointer indicates "nothing there", so of course it returns zero. Otherwise, we return 1 (for the node itself) plus the number of nodes in the left subtree plus the number of nodes in the right subtree. This sort of recursion, direct recursion we might call it, where a function calls itself, does not need forward declarations. A function is already declared within its own body. The time you need forward declarations is when you have two or more functions which are mutually recursive, as in struct treenode { ... struct exprnode *expr; ... }; struct exprnode { ... struct treenode *tree; ... }; void free_treenode(struct treenode *); void free_exprnode(struct exprnode *); void free_treenode(struct treenode *tn) { ... if (...) free_exprnode(tn->expr); ... } void free_exprnode(struct exprnode *xn) { ... if (...) free_treenode(xn->tree); ... } (We actually need the forward declaration only for free_exprnode, in this example, but I would tend to provide them both anyway.) This is where it really matters that each invocation of a block gets its own copy of each variable declared in that block with automatic storage duration. None of the above examples actually declare any block-local variables, but here's one that does: struct nway_node { struct nway_node *children[8]; ... }; void free_nway(struct nway_node *n) { int i; if (! n) return; for (i=0;i<8;i++) free_nway(n->children[i]); free(n); } Here, it's important that each recursive call to free_nway have its own i, because it would break things badly for an inner call's loop to change the i being used by a partly-done outer call's loop. ---------------- There are a few individually small topics I want to cover, so I will be hopping around from topic to topic here. I've remarked that the " " syntax actually creates an anonymous array of chars, containing the string's contents plus a trailing '\0'. What is that trailing '\0' there for? It's there so that various things can find the end of the string. This is not, strictly, necessary. There are languages that represent strings with the length kept separately from the contents. But C chooses to handle strings with a terminator rather than by keeping the length separately. Most of the string support assumes that terminator is there, and the " " syntax puts it there. (It's entirely possible to work with separate-length strings in C. You just can't use much of the existing string support - though we haven't discussed it yet.) I've written that you can create pointers to any type. What about functions? Certainly they can be thought of as types; we have only to ask what type free_nway in the last example above has to see how reasonable this sounds. And, indeed, C does have function types, and pointer-to-function types (though function types, in contrast to pointer-to-function types, are somewhat second-class citizens). The syntax for declaring a pointer to a function is a little cryptic-looking when you first encounter it. The usual rule for C is that "declaration mirrors use". As a really simple example, if we write int i; then that means that i is int. If we write int a[8]; then a[i] is an int. If we write int *ap; then *ap is an int. For functions, we need parens, because the parens for function parameters have higher precedence than the * for a pointer. For example, if we want fn to be a function taking an int and a float and returning nothing, we'd write void fn(int, float); so for fnp to be a pointer to that same kind of function, we need void (*fnp)(int, float); and we could then set it with fnp = &fn; and we might call it with (*fnp)(3, 2.375) The declaration-mirrors-use aspect is that if the function is fn and fnp points to fn, then we'd use *fnp the same way we'd use fn, except that we need parens in some cases because of the precedence rules. In this respect it's just like, say, ints: if i is an int and ip points to i, then we can use *ip the same way we'd use i, except that in some cases we need parens around it because of the precedence rules. (If we wrote void *fnp(int, float); then, because of the precedence rules, that would mean void *(fnp(int, float)); thus making it a declaration of a function called fnp, taking int and float and returning pointer-to-void.) This is what I was talking about when I wrote, back when I was first introducing functions, that just naming a function with args in parens was not the only way to call it; you can also call it by creating a pointer to it and then calling through that pointer. Indeed, something happens with functions that's reminiscent of what happens with arrays. Remember how an array actually turns into a pointer (to its first element) and then subscripting actually uses that pointer? Similarly, when you call a function by writing its name, what happens, strictly speaking, is that the name turns into a pointer to the function and the call then calls through that pointer. (As this implies, you can actually call through a function pointer without needing an explicit *; instead of (*fnp)(3, 2.375) you can write fnp(3, 2.375) though I recommend avoiding that, because it's misleadingly similar to calling a function named fnp.) One important note is that pointers to functions (which I haven't talked about much yet) may be fundamentally different beasts from pointers to objects. This is more likely in a Harvard architecture than in a von Neumann architecture, but even in a von Neumann machine it is far from impossible. It is possible to convert between object pointers and function pointers with casts, but there is no guarantee that doing so results in anything useful in either direction, much less both directions - the general intent is that what it does should be unsurprising to someone who knows the underlying architecture in question. (But, as I remarked above about nil pointers, some people have a tendency to confuse a _conceptual_ pointer-to-function with its _implementation_. Since most current machines implement function pointers as just the address of the function's code, and code and data live in the same address space, they're all "just memory addresses". This is fine as long as you're on that kind of machine, with that kind of compiler. But change machine or change compiler and sloppy code that confuses the two can bite you badly.) It also is possible to convert from an integer to a pointer, or the other way, with a cast. Except for converting null pointer constants to pointers, the results are entirely implementation-dependent. (Note that not all integer zeros are null pointer constants. For example, if j is an int variable, (int *)j is not necessarily a nil pointer to int, even when j contains zero. The value being cast is an integer expression with value zero, but not an integer _constant_ expression, and thus not a null pointer constant.) We've been covering a lot of material. It's time for an exercise to help it stick. -> Exercise: [XXX Think of a good exercise for here] I've mentioned that there are integer types other than int. I even talked about one of them (char). Let's learn the full details. Integer types are divided into two classes: signed and unsigned. There are five standard signed integer types (as of C99, at least): signed char, signed short int, signed int, signed long int, and signed long long int; for the last four of those, the `signed' adjective is optional, and on some systems it effectively is for char as well (see below). (The "long long int" name is, as I understand it, a historical accident, a kludge in a popular compiler that got raised to ascended glitch status and enshrined in the spec.) Corresponding to each signed integer type, there is an unsigned integer type, obtained by changing `signed' to `unsigned', or adding `unsigned' if the signed type is expressed without `signed' - or, for the special case of char on certain architectures, simply removing `signed'. The unsigned types use the same amount of storage as, and have the same alignment requirements as, the corresponding signed types. (We haven't yet discussed alignment requirements. Also, there is another standard integer type, _Bool, to which additional magic applies; see below.) The type `char', so-called "plain" char, declared with neither `signed' nor `unsigned', is always exactly like either signed char or unsigned char, but is a distinct type from both. (Which it's equivalent to is machine-dependent, and in some cases can depend on other things, such as compiler invocation flags.) This is a concession to portability. On some machines, it is cheap to treat a byte as signed but expensive to treat it as unsigned; on others, it's the other way around. The intent is that plain char be whichever one is cheaper, more natural for the architecture in use, but that the explicitly signed and unsigned variants are available for cases where the signedness matters even if that means using the `expensive' kind. (On most architectures you're likely to run into, signed char and unsigned char are so close to identical in performance terms that it's not something you should worry about unless and until performance testing demonstrates it's a real issue.) The `int' keyword is optional for the short int, long int, and long long int types, signed and unsigned versions both. It's even optional for int, provided you specify the signed or unsigned keyword. The minimum sizes for these types, according to C99, are: char, 8 bits; short, 16 bits; int, 16 bits; long int, 32 bits; long long int, 64 bits. They all may be bigger - those are lower limits - and, indeed, these days int is almost universally 32 bits. (There are even a few unusual systems where char, short, int, and long are all the same size.) However, C also promises that char is no bigger than short, short is no bigger than int, int is no bigger than long, and long is no bigger than long long. There is also _Bool, which has some unusual properties. It can store only the values 0 and 1, and, when converting any other type to _Bool, all nonzero values are converted to 1. (As this implies, types that cannot be compared to 0 - structs, for example - cannot be converted to _Bool. When converting between other integer types, converting from a wider type to a narrower type usually just drops the high bits, though when signed types are involved it's not always that simple. _Bool is special. It's also one of the very few identifiers the standard specifies which begins with an underscore; we saw a few more of them when writing the keyword-counting program, and there are a very few others.) I mentioned, above, that (integer) arithmetic overflow usually just reduces modulo some modulus, but that C does not promise that. For unsigned types, C actually _does_ promise that; unsigned types are defined to perform arithmetic in pure binary, with overflow discarded. Signed arithmetic does not promise that, though it's how most current machines work. Similarly, when introducing floats, I said there were two major floating-point types, but that I would talk about only floats at that point. The other major floating-point type is called `double', and on most machines it has twice the storage requirements and both greater range and better precision than float. There is also `long double', which is used comparatively seldom. C promises that every possible float value is also a possible double value, but does not promise the converse (and on most machines the converse is not true); similarly for double and long double, though there are probably more machines on which double and long double are identical than there are on which float and double are identical. When long double differs from double, it typically differs by having more precision, but that is not promised. I said `major' floating-point types, because C also has complex floating-point types - that's "complex" in the mathematical sense of being made up of a real part and an imaginary part, not in the everyday sense of being complicated, though non-mathematicians certainly may argue that the former implies the latter! Unless you're working with something whose underlying math calls for it, you may never see complex types in the wild. I can't recall ever seeing them used except to make a point - but I tend to stay away from FFTs and phasors and the like where complex numbers get used routinely. Hence my perception of them as minor - and, accordingly, I'm going to defer discussion of complex types to the appendix. The next thing I want to talk about is a variation on structs. They're called unions. The name comes from the underlying concept. A union of an int and a float, for example, is an object that can take on any int value or any float value: the set of values it supports is the union of the sets of values supported by those two types. They are written just like structs, except with the `union' keyword instead of `struct': union int_or_float { int i; float f; }; If we then do union int_or_float v; then we can assign to v.i or v.f. In that respect it's just like a struct. The difference is that if we read any member other than the one we most recently stored into, what happens is undefined. Most implementations do a "treat the bits as": they use the same underlying memory for all the arms of the union and reading a member other than the most recently written one just gives you whatever happens to be in that memory. But that is not something C promises. For example, if we do v.f = 3.75; printf("%d\n",v.i); then the value printed is undefined by C. (Of course, it may be well-defined for a particular implementation on a particular hardware platform; on many machines, it will let you pry into the underlying representation for floats.) C does not provide any way to tell which member of a union was last stored into. If you want that, the simplest thing to do is probably to wrap it in a struct: struct val { int val_is_float; union { int ival; float fval; } u; } s; ... s.u.fval = 2.375; s.val_is_float = 1; ... ... s.u.ival = 81; s.val_is_float = 0; ... ... if (s.val_is_float) { ...use s.u.fval ... } else { ...use s.u.ival ... } ... This sort of thing is typically called a discriminated union, and leads very naturally into the next thing I want to talk about. These are enums. The name `enum' is an abbreviation of `enumeration', just as `struct' is an abbreviation of `structure'; enums give names to an enumerated list of possibilities. The syntax looks a lot like structs or unions, but the members don't have types (and the syntax is slightly different): enum foo { FOO_ONE, FOO_TWO, FOO_THREE }; This declares a type, `enum foo', which can take on any one of the three values FOO_ONE, FOO_TWO, and FOO_THREE. The reason I say that discriminated unions lead naturally into enums is that enums are often used as the discriminant. For example, if we're doing three-dimensional geometry, we may want to represent points using Cartesian, cylindrical, or spherical coordinates: enum coordtype { CT_CARTESIAN, CT_CYLINDRICAL, CT_SPHERICAL }; struct coord { enum coordtype type; union { struct { // if CT_CARTESIAN float x; float y; float z; } cart; struct { // if CT_CYLINDRICAL float r; float a; float z; } cyl; struct { // if CT_SPHERICAL float r; float theta; float phi; } sphere; } u; }; If you want, you can specify the actual values used for the various enum members: enum foo { FOO_ZERO = 1, FOO_ONE = 2, FOO_TWO = 4, FOO_THREE = 8, }; (You can also have a comma after the last one, as in the above example; this makes it slightly easier to add and remove members.) If you don't specify a value for an enum member, its value is one greater than the previous member's value; if you don't specify any value for the first member, its value is 0. The individual constants - the members of the enum, such as FOO_ONE and FOO_TWO above - have type int, and you will see enums used to provide manifest constants, something like #defines. (Personally, I don't like that; I prefer to think of types more abstractly. But that appears to be another personal quirk.) This is now enough to build something that's actually useful: a calculator program. -> Exercise: Build a calculator program. A run might look like this (where the -> is the prompt): -> x = 5 5 -> y = 7 7 -> x*x + y*y 74 -> 1/1 + 1/2 + 1/3 + 1/4 + 1/5 + 1/6 + 1/7 + 1/8 + 1/9 + 1/10 2.92897 -> To keep things simple, I'd suggest supporting only 26 variables, one for each letter. (There are many possible ways to build such a thing. You may need to research expression-parsing techniques.) If you want to really stretch yourself, here are three possible directions to extend it: 1) Support multi-letter variable names: -> foo = 1.2 1.2 -> bar = 9.8 9.8 -> foo + bar 11 -> 2) Support rational fraction values (integer/integer) as well as integers and floats: -> 1/1 + 1/2 + 1/3 + 1/4 + 1/5 + 1/6 + 1/7 + 1/8 + 1/9 + 1/10 7381/2520 -> 3) Support some additional arithmetic functions, such as square root: -> sqrt(2) 1.4142136 -> Of course, if you think of some other direction you want to extend it, go for it! The more you do the more you'll learn. ---------------- It's time to finally describe something I've mentioned briefly above, and implicitly depended on all along: side effects. Things such as operators applied to operands, or function calls, have, or at least can have, explicit inputs and explicit outputs. If we write 4 + 8, for example, then 4 and 8 are explicit inputs and the explicit output is the sum (12 in this case). Most operators work this way: all inputs and outputs are explicit, and they don't affect anything else. To use the terminology I want to introduce here, they have no side effects. But some do have side effects. Assignments, for example, don't just return a value; they have the side effect of storing a new value in a variable (for generalized meanings of "variable" - in an lvalue, if you want to get technical). printf() calls have a different kind of side effect - they not only return a value (which I still haven't discussed) but also affect the world beyond the program (in the case of printf, by producing output). The concept of a side effect is important mostly in that it relates to undefined behaviour and sequence points. I've touched briefly on undefined behaviour before, but not sequence points. Sequence points are a concept used by the C standard as a formalization of the intuitive notion of things being done in a specific order, while still allowing for variation in order where it doesn't matter, or where implementations have always differed from one another. For example, if we write s = *ip++ + *jp++; there are three side effects involved: ip is incremented, jp is incremented, and s is assigned to. (There are arguably two more: a value is fetched from wherever ip points and a value is fetched from wherever jp points. In some cases those are considered side effects, but more often they're not, since they (generally) won't actually *change* anything.) However, if we write s = *ip++ + *ip++; then we have three side effects again, but two of them are affecting the same thing (two increments applied to ip). Existing implementations differed in their behaviour in cases like this, so the C standard did not specify any particular behaviour. (The questions here are (a) where are the two values we add obtained from? and (b) how does ip change from just before that statement to just after it?. But even those questions assume things not promised by the language. This is undefined behaviour, which means there's no guarantee that anything is read from anywhere, or that s or ip is changed at all.) But we do want code like s = *ip++; s += *ip++; to be well-defined. We want it to be OK to affect the same thing twice, provided the two effects are sufficiently separated (as in the above, where having the two side effects in the same statement is bad but having them in separate statements is fine). Sequence points are a formalization of the loose "sufficiently separated" idea above. Sequence points are conceptual times during a program run, points at which certain promises apply. For example, an object must be modified at most once between any two sequence points (if not, behaviour is undefined) - but, at each sequence point, all previous side effects must have finished and all future side effects must have not yet started. For example, in the sequence int i; int *ip; int j; i = 44; ip = &i; *ip = 7; j = i; we have the question of what value j gets on the last line. To be useful, we want it to be 7, which means that the assignment to *ip must have finished, in the sense that it's affected the value of i, by the time the last assignment reads the value of i. There is a sequence point between the last two assignment statements (and others earlier, but it's the last one I'm focusing on here), which is a formalization of this promise: the store through *ip must have finished, but the read implied by the j=i assignment must have not yet started. This stuff mostly affects optimizers, but it's important that you be aware the issues exist, because, sooner or later, you'll end up in a situation where they matter. So, here's the full list of sequence points, from C99. Some of these may not mean much to you yet. - After all the arguments to a function call have been evaluated but before the call itself happens. - After the first operand of a , (comma), ?: (conditional), && (logical AND), or || (logical OR) operator. - The end of a full declarator. - The end of a full expression in - An initializer - The expression in an expression statement - The controlling expression in a switch statement - The test in an if, while, or do-while statement - Each of the three expressions in a for statement - The expression in a return statement - After the actions associated with a printf/scanf format spec. - Immediately before the return of a library function. - Immediately before and after each call to a comparison function, and between any call to a comparison function and any data movement, for bsearch() and qsort(). You may be wondering why the last two items are listed, since they would appear to be covered by the various earlier listings involving functions. This is because the calls in question do not have to be implemented in C and thus the promises implied by those sequence points might not apply - but the calls in question are specified by the C standard, so the question needs to be addressed. I mentioned, above, that we can create pointers to any type. I then proceeded to talk about pointers to ints, to floats, to structs, and to void. But what about pointers to other types? Pointers to pointers are conceptually fairly simple. They're just like pointers to ints or pointers to floats except that the thing being pointed to is itself a pointer. For example, if we have int i; int *ip; then we can ip = &i; But now, if we want to pass ip to a function that sets it to something, how do we do that? Well, if we wanted to have a function that set an int, we'd pass a pointer to it: void set_int(int *ip) { *ip = ...value...; } ... int i; set_int(&i); We can do the same with a pointer-to-int variable instead of an int variable. We just need another *: void set_int_ptr(int **ipp) { *ipp = ...value...; } ... int *ip; set_int_ptr(&ip); Pointers to arrays and pointers to functions look more complex, but this is largely because of the syntax (declaring an array or declaring a function puts text after, as well as before, the declared identifier) and precedence (array [] and function () bind tighter than the * indicating a pointer). When we declare an int variable int var; the type appears entirely before the variable. Even if it's a pointer-to-int int *varp; the type still appears entirely before the variable. But with an array int arr[4]; the type appears partially before ("int") and partially after ("[4]") the name of the declared variable ("arr"). Similarly, with functions, int fn(void); the type appears partially before ("int") and partially after "(void)" the declared name ("fn"). With a simple type, we can just tack a * onto the end of the type to get a pointer to it: int -> int *. But what's really happening is that we're adding a * just before the declared name - it's just that, with a simple variable, that's the same thing as tacking a * onto the end of the type. But with an array or a function, tacking a * on before the declared name puts it in the middle of the type, since the type is partly before and partly after the declared name. Worse, precedence causes trouble again. If we write int *arr[4]; we haven't declared arr to be a pointer to anything; we've declared it to be an array (of four pointers-to-ints), because the [] for arrays binds tighter than the * for pointer. We have to add parens if we want to declare a pointer to an array: int (*arrptr)[4]; Similar remarks apply to functions. For example, int *foo(int, float); declares foo as taking an int and a float and returning pointer-to-int. If we want foo to be a pointer to a function that takes int and float and returns int, we need to write int (*foo)(int, float); The syntax can get a bit visually confusing in extreme cases. For example, if we have an error-handling function, which takes a pointer to a struct err and returns an int, and we want to write a function ("set_err", say) that takes an error-handling function pointer and returns another one, we wind up with (I'm sneaking one more thing in here, which we haven't seen per se but which follows logically from what I've said above) int (*set_err(int (*)(struct err *)))(struct err *); which breaks down as: At the outermost level, we have a pointer to a function ----(*------------------------------)(------------); which takes a pointer to struct err (struct err *); and returns int int What is this pointer? It's the return value from set_err set_err(---------------------) where set_err takes a pointer to a function ----(*)(------------) which takes pointer to struct err struct err * and returns int int The thing I was sneaking in was the syntax with (*) in it for describing a pointer-to-function argument. This follows logically from what I've said, in that, if we want arg to be a pointer to a function (for simplicity, I'll make it a function that takes an int and returns void), we would write void (*arg)(int); In a function declaration arglist, we could do the same thing: ... fn(..., void (*arg)(int), ...) But I also mentioned that, in a declaration that is not a definition, you can (and may sometimes want to) skip the formal argument names. That applies here too, and leads to ... fn(..., void (*)(int), ...) or, in the error-handling example, to the syntax I gave. Such code can be significantly clarified by use of typedefs... ...it's time to learn about typedefs. typedef is a way to define a simple short name for a type, so you can use the type without having to give the potentially long description of it every time. (It does not introduce any new types; it just introduces new ways to write types.) You do this by writing what looks like a variable declaration, except with the keyword `typedef' in front: typedef int COUNT; typedef float DISTANCE; typedef struct pt POINT; The name that would be the variable name if it weren't for the typedef keyword then becomes a name for the type that hypothetical variable would have. Thus, after the above, we can do COUNT n; // equivalent to int n DISTANCE maxd; // equivalent to float maxd POINT vertices[3]; // equivalent to struct pt vertices[3] This can be used to provide a little bit of conceptual type abstraction, as in the COUNT and DISTANCE versions above. (It does not provide anything more; C would be perfectly happy with our writing something like maxd+n. This is the sort of thing I meant when I wrote that typedef does not create new types, just new names for types we could have named directly.) But typedef can also be used to provide a short name for an otherwise fairly complex-to-write type, as in typedef int (*CALLBACK)(void *, int, struct state *); which makes CALLBACK a type for a pointer to a function with a particular calling signature (taking pointer-to-void, int, and pointer to struct state, and returning int). Then, instead of writing int (*cb_pre)(void *, int, struct state *); int (*cb_post)(void *, int, struct state *); void set_callbacks( int (*pre)(void *, int, struct state *), int (*post)(void *, int, struct state *) ) { cb_pre = pre; cb_post = post; } we can write CALLBACK cb_pre; CALLBACK cb_post; void set_callbacks(CALLBACK pre, CALLBACK post) { cb_pre = pre; cb_post = post; } This also has the advantage that, if we find that we need to change the signature of a CALLBACK, we can change it in one place and much of the infrastructure doesn't need to be touched. (The actual definitions, and sometimes declarations, of such functions will need changing, but there's no way around that.) This makes something like set_err, above, substantially clearer: typedef int (*ERR_HANDLER)(struct err *); ERR_HANDLER set_err(ERR_HANDLER); If we prefer, we can make ERR_HANDLER the function type, rather than a pointer to it, and put the *s in the set_err declaration: typedef int (ERR_HANDLER)(struct err *); ERR_HANDLER *set_err(ERR_HANDLER *); Pointers to arrays are less used, but they too are clarified by typedefs: typedef char (*ARR_CHAR_256_PTR)[256]; ARR_CHAR_256_PTR ptr_array; [XXX It's past time for an exercise. Think of a good one here.] ---------------- I think it's time to learn more about the preprocessor. We've been using it in a few ways all along, most visibly in the form of the #include lines, but it can do quite a lot, and, so far, we've kept it very much in the background. The preprocessor is controlled by lines beginning with #. This is almost the only place in the language where line breaks are significant. The preprocessor is, in some respects, line-based, whereas most of the rest of the language considers line breaks just another form of whitespace. (Not quite all; there are a very few other things line breaks matter for, such as \ line splicing (which I haven't discussed yet) or " " strings, but the preprocessor is the big place where line boundaries matter.) So far, we've seen two kinds of lines beginning with #. One is #include lines such as the #include we've been using since the very first example; the other is #define lines such as the #define MAXKEYLEN 10 from the keyword-counting program. There are a bunch more, all of which have # as their first nonblank character. (Historically, old versions of the preprocessor required that the # be the first character of the line, not just the first nonblank character of the line. There is still a tradition of writing preprocessor lines with no whitespace before the #, though it's hardly universal.) Following the # comes optional whitespace, then a keyword specifying what kind of line the preprocessor line is, and, depending on the keyword, possibly more stuff. (Again, historical practice differs; some really old preprocessors required the keyword to follow the # with no whitespace in between.) Of the two kinds we've seen so far, #include is possibly the simpler. It has two forms. Each one has a name - usually, a file name - inside delimiters. The delimiters can be either < > or " ", as in #include or #include "utils.h" Historically, the names have been file names, and the difference between < > and " " has been that the < > form looks for the file only in system-wide places whereas the " " form also looks in places local to the file being compiled (typically, the same directory as the C source file). This is still the commonest implementation, and is what you will usually see, but the C standard is a bit more abstract, because not all implementations have the kind of filesystem that paradigm assumes. Formally speaking, the < > form treats the part between the < and > in an implementation-dependent manner, except for a few names which are specified by the standard (such as ). They need not exist as files anywhere, though that is the commonest implementation, and they are sometimes spoken of as "include files". (You may also see other terms for them, such as "header files"; their names traditionally end with .h, with the h presumably being the first character of `header'.) The original use of #include, what it still does in most implementations, and what it is even now defined to do when " " delimiters are used, is to take the contents of the file named and process it as if it had occurred inline in place of the #include line. So, if we have a C source file (let's assume for concreteness that it's called pts.c) containing #include "structs.h" struct pt newpoint(void) { ... } and a file structs.h in the same directory (include files traditionally are named ending with .h, presumably for `header') containing struct pt { double x; double y; double z; }; then the effect, when compiling pts.c, is as if it contained struct pt { double x; double y; double z; }; struct pt newpoint(void) { ... } So, why would we do this? Explaining the motivation for this will mostly have to wait until I get to talking about separate compilation. For the moment, I mention it mainly because #include is the commonest visible manifestation of the preprocessor we've seen. There are 12 different kinds of preprocessing lines (typically called `directives'): #define #error #include #elif #if #line #else #ifdef #pragma #endif #ifndef #undef (Well, 13 if you count the null directive, a # with nothing but optional whitespace and comments following it; a null directive does nothing.) I'll describe each of them, but there is some background that would help first. The preprocessor is a textual processor that runs over the source code before - at least _conceptually_ before - the compiler proper runs. Thus, for example, a preprocessor line affects only that part of the program which is textually after that line, regardless of run-time execution order. It is a relatively limited macro processor; for example, it is difficult, albeit not quite impossible, to loop in the preprocessor. (I have seen it done, but only in an Obfuscated C Code Contest entry, where it was difficult to do, difficult to read, remarkably slow to compile, and crippled compared to most languages' looping constructs.) Outside of preprocessor lines, all the preprocessor does - all it can do - is macro expansion. Still, it is useful enough to significantly increase the expressive power of the language. As a macro-expansion pass, possibly the most critical preprocessor line is #define. We have seen this one, in a limited form; recall, above, #define MAXKEYLEN 10 #define NKEYWORDS 37 or (slightly more complicated) #define NKEYWORDS (sizeof(keywords)/sizeof(keywords[0])) This kind of #define - `object-like' is the C99 term - is distinguished by having the macro name, the identifier after #define, followed by a character other than an open paren. (As the last of the above three examples implies, whitespace counts as something other than an open paren, even if the whitespace is itself followed by an open paren. This is one of the very few places in C where whitespace between two tokens has semantic significance even when the tokens wouldn't merge if the whitespace were removed.) Such a #define just makes the identifier expand to a string of tokens. As this description in turn implies, preprocessor macro expansion operates at the token level; it is not quite fair, strictly, to call it a textual macro-expansion pass. There is no way to make the preprocessor take action based on only part of a token; there is, for example, no way to make the preprocessor turn FOO3 into something based on 3 and FOO7 into something based on 7 and FOO99 into something based on 99 without #defining each of FOO3, FOO7, and FOO99 separately. There also is no way for a preprocessor macro to expand to preprocessor lines - a # generated by a macro expansion is never taken as the # indicating that a line is a preprocessor line. (Also, preprocessor lines never occur within comments. A line that looks like a preprocessor line inside a comment is just part of the comment.) So, if we #define MAXKEYLEN 10 then, after that, when MAXKEYLEN occurs as an identifier, it is replaced with the token 10. Thus, writing i = MAXKEYLEN; turns into i = 10; and char buf[MAXKEYLEN+3]; turns into char buf[10+3]; Like a lot of C, this can be used to make code clearer; it can also be used to make code less clear. For example, if we write #define i if (foo( #define j return 0; } then we can write code like int main(void) { i 7) < 10) { j bar(); return(0); } that superficially looks like garbage. Taking advantage of this sort of thing to write cryptic code is, well, no better than writing cryptic code any other way. The above describes so-called object-like macros. When the identifier being defined by a #define line _is_ followed immediately by an open paren, it defines a different kind of macro, called a function-like macro. As implied by the name, these look and act more like functions, whereas object-like macros look (and, usually, act) more like objects. A function-like macro definition consists of the identifier being defined followed by a formal argument list in parens, with no whitespace between the identifier being defined and the open paren. Unlike the formal arguments to a function, though, there are no types attached, just the identifiers - in the preprocessor, there are no types, or, to put it another way, the only type there is is `string of tokens'. In the replacement text, when an identifier that's a formal argument occurs, it is replaced with the token sequence that's used as the corresponding actual argument in the call. For example, if we write #define D2(x,y) (((x)*(x))+((y)*(y))) then, if we write i = D2(3,j+2); it will expand to i = (((3)*(3))+((j+2)*(j+2))); As this example demonstrates, it is usually a good idea to put parens around uses of the formal arguments in the expansion. If we had skipped that here, writing instead #define D2(x,y) ((x*x)+(y*y)) then i = D2(3,j+2); would have expanded to i = ((3*3)+(j+2*j+2)); which is probably not what was intended and certainly not what the call looks as though it is trying to do. As remarked above when discussing NKEYWORDS and sizeof, it is usually a good idea to put parens around the expansion when a macro expands to an expression. This is just as true for function-like macros as it is for object-like macros, and for much the same reason; consider #define D2(x,y) (x*x)+(y*y) after which i = r2 - D2(x,y); expands to i = r2 - (x*x)+(y*y); which is probably not what was intended and definitely not what the call looks as though it is trying to do. It is not possible to have the same name defined as both an object-like macro and a function-like macro at the same time. (It makes conceptual sense to do so, but that is not how the C preprocessor is designed.) Also, macro expansion does not happen inside character strings (" " strings) character literals (' ' constants), or comments. You can write a comment on a #define line, but the comment is effectively replaced by whitespace before the #define happens. It is perfectly permissible for the expansion text to be empty (to consist of nothng except possibly comments or whitespace), for either an object-like macro or a function-like macro. In this case, the logically consistent thing happens: the macro disappears - it is replaced by zero tokens. (Or almost. This is a slight simplification, related to token pasting, something I haven't yet described.) Macro names do not respect language keywords (the list, way back, containing auto, break, case, char, const, etc); whether an identifier is a language keyword or not is completely irrelevant to the preprocessor. There is another form of function-like macro, one where the formal argument list ends with ..., but I'm going to defer discussion of that until a bit later. First, I'm going to describe some other aspects of the preprocessor. Next up is #undef. This one is comparatively simple: #undef takes an identifier and undoes any macro definition for that identifier. (If there is none, it is not an error, but nothing happens.) As I remarked above, there is no way to have an identifier defined as more than one kind of macro, so there is no ambiguity. (Also, while I haven't explicitly stated this before, there is nothing like scoping for preprocessor macros. It's straight beginning-to-end macro processing.) Thus, if we write int i; int j; i = 1; printf("%d\n",i); #define i 100 printf("%d\n",i); #undef i printf("%d\n",i); then the first printf prints 1 and the second one prints 100, but the third one prints 1. (The i in the second printf call gets replaced by the preprocessor before it has a chance to become a reference to the variable of the that name.) Next is conditional compilation. These look like conditionals, but, remembering that the preprocessor is line-based and takes effect in textual order, not execution order, they control what gets compiled. The simplest form is probably #ifdef (or #ifndef), #else, and #endif. For example, we might see ... #ifdef HAVE_FIZZLE j = fizzle(thing); #else // No fizzle() available, just use 1. j = 1; #endif ... #ifdef is "if defined". So, if a preprocessor macro HAVE_FIZZLE is defined at that point, the first line, the one that calls fizzle(), is compiled in and the other two lines aren't; otherwise, it's the other way around. This sort of thing is done commonly enough that a C compiler can be assumed to have a way to specify, when you run it, that certain names are defined. With most Unix-family compilers, for example, you can do this with -D, as in cc -o fizz fizz.c -DHAVE_FIZZLE Like the else block in an if statement, the #else and the second block of code are optional. Similarly, there's #ifndef, which is "if not defined", the converse of #ifdef. And, more generally, there's #if, which takes an expression and conditionally includes code based on it. But, since the expression is evaluated in the preprocessor, it is a bit different from a normal C expression. Casts are not permitted, because at that stage there are no types (or, rather, there is only one type, `integer'). There is one additional operator available, `defined', which is a unary prefix operator; it takes an identifier, optionally enclosed in parens, and returns 0 if it is not defined or 1 if it is. And, finally, there are no language keywords; as I mentioned above, the preprocessor doesn't recognize language keywords as different from other identifiers - the only identifier that is special in preprocessor expressions is `defined'. (As this implies, the sizeof operator is not available.) The expression is processed by first expanding macros and handling `defined' operators. Once that is done, all remaining identifiers are replaced by `0' tokens (that is, tokens representing the number 0, not `no tokens'). Then the expression is evaluated. If the result is nonzero, the #if is true and includes its first block of lines; if not, it is false, and includes the #else block, if any. (There is no #ifn; instead, just use ! on the test expression.) Because preprocessor conditionals are not statements and do not have the structure of statements, doing else-if is a bit more awkward; with what I've described so far, it ends up being #if ...test 1... ... #else #if ...test 2... ... #else #if ...test t... ... #else ... #endif #endif #endif This is a bit ugly, so there is also #elif, which is "else if": it functions like the #else/#if pair above, but doesn't require an additional #endif: #if ...test 1... ... #elif ...test 2... ... #elif ...test t... ... #else ... #endif The remaining three preprocessor lines see comparatively little use. The only one you're likely to need soon is #error, and even that is a bit unlikely. #error generates a compile-time error. It is, of course, most useful when it's conditional, as in #if defined(FOO_KIND_ONE) int foo(int x, int y) { ...code for kind one... } #elif defined(FOO_KIND_TWO) int foo(int x, int y) { ...code for kind two... } #elif defined(FOO_KIND_THREE) int foo(int x, int y) { ...code for kind three... } #else #error "No foo() kind specified!" #endif This is, perhaps, most commonly used to adapt to OS-specific things. Most OSes arrange to pre-define certain macros, typically something like __NetBSD__ or __linux__ or __Solaris__, leading to things like #if defined(__NetBSD__) || defined(__FreeBSD__) || ... ...BSD code... #elif defined(__linux__) ...Linux code... #elif defined(__VMS__) ...VMS code... #elif defined(__sun__) ...SunOS code... #else #error "Don't know this OS" #endif There is also #line, which is useful mostly for programs that generate C code based on something else. Its syntax is fairly simple: first, the text on the line after #line is macro-expanded and has comments converted into whitespace. Then, the body of the line must consist of either a digit sequence (an integer, approximately) or a digit sequence followed by a " " string literal. This changes the compiler's idea of the line number (the number) and optionally file name (the string) for the next line and, with the line number incremented as usual, for following lines up until the next #line directive. The point of #line is that compiler error messages can refer to a more useful place. For example, there is a parser generator called `yacc', which among other things permits embedding fragments of C code into its input language. Its output is a C program, and most implementations of yacc generate #line directives so that the embedded C code fragments are recognized as coming from yacc's input file rather than from the generated C file. (This matters mostly for errors, but can also help debuggers.) Finally, there is #pragma. This is a kind of escape hatch. If the token after #pragma is STDC, this controls one of a few possible compilation options, typically controlling details such as exactly how floating-point expressions get evaluated. If the token after #pragma isn't STDC, what happens is entirely compiler-specific. One popular compiler, for example, uses this to turn warnings on and off for sections of code, making it is possible to build most of a file with a specific warning turned on, but turned off for a few lines here and there (or the converse, though that's less common). Because it is not possible to generate preprocessor directives via macro expansion, there is another way to generate pragmas: there is a unary prefix operator, _Pragma, which takes the form _Pragma ( string-literal ) The contents of the string literal, after stripping the " " and undoing \" and \\ escapes, are then processed as if they were source file text, with the resulting sequence of preprocessing tokens treated as if they were the body of a #pragma directive. The original four tokens are then dropped. There are three more things to describe before I leave the preprocessor for other topics: token pasting, stringizing, and macro varargs. Token pasting is used to construct tokens out of multiple pieces. In the earliest versions of the preprocessor, comments were entirely deleted, rather than being converted to whitespace, and people used this to construct tokens from multiple pieces, as in #define CALL_FN(fn) function_/**/fn(arglist) where the implementation happened to perform argument replacement before it stripped comments, so that ... CALL_FN(xyz) ... turned into ... function_/**/xyz(arglist) ... and then ... function_xyz(arglist) ... The people involved in standardizing C felt that this was too common and/or useful to break entirely, but (correctly) saw problems with doing it that way. So they came up with an alternative way: token pasting, it's called. In a function-like macro's replacement, after replacing formal arguments with their actual argument token sequences, if an argument is preceded or followed by ##, the tokens on either side of the ## are pasted together to form a single token. (If they are such that the resulting `token' is not a valid token, what happens is undefined.) Thus, the way to write CALL_FN, above, in standard C is #define CALL_FN(fn) function_##fn(arglist) There are details, such as exactly when macros get expanded in what text; I'm going to defer those to the appendix, since they will not matter to a lot of you at all, and won't matter to the rest until you've developed some level of expertise in the language. I also mentioned, above, that arguments containing no tokens uncovered a detail involving token pasting; that too I will defer. Stringizing is converting a macro argument to a string. This is a replacement for another historical artifact. In early preprocessors, macro arguments were replaced even inside string literals, as in #define PRINT_VALUE(expr) printf("expr = %d\n",expr) which would lead to PRINT_VALUE(a[i]+7); turning into printf("a[i]+7 = %d\n",a[i]+7); But, again, standardizing C led to changing this. In standard C, macro argument replacement does not happen inside string literals, but two things were added to the language to support the desire to do that kind of thing: stringizing and string literal concatenation. Stringizing is a preprocessor facility. In a function-like macro replacement, if a formal argument is preceded by a # operator, the # operator plus that particular instance of the formal argument are converted to a string literal whose contents are the actual argument's spelling. This means that, for example, the above effect can be obtained with #define PRINT_VALUE(expr) printf("%s = %d\n",#expr,expr) which converts PRINT_VALUE(a[i]+7); into printf("%s = %d\n","a[i]+7",a[i]+7); There is processing performed to handle " or \ in the argument, so that, for example, #define FOO(x) if (! (x)) printf("%s failed!\n",#x) turns FOO("0123456789"[dval] < '8'); into the equivalent of if (! ("0123456789"[dval] < '8')) printf("%s failed!\n","\"0123456789\"[dval] < '8'"); The important part here is that the "s in the argument end up being \"s in the string, so that it ends up resulting in a string literal whose content is the spelling of the argument expression. I had trouble keeping it straight which of # and ## was which until someone pointed out to me that the one with one # is the one that takes one operand and the one with two #s is the one that takes two operands. I mentioned string literal concatenation, above. It is not, strictly, part of the preprocessor, but it is related to stringizing macro arguments, so I'll discuss it here. This is fairly simple: when two " " string literals appear adjacent to one another, their resulting character sequences are effectively concatenated, resulting in a single string. For example, if we write printf("foo =" " %d\n", foo); then this is equivalent to printf("foo = %d\n", foo); This makes it possible to generate a string literal containing a macro argument but also additional text, as in #define PRINT_VALUE(expr) printf(#expr " = %d\n",expr) which is a more literal conversion of the original example. (It is better to use the other form, with %s, in this particular case; consider what happens to the call PRINT_VALUE(dvalue%dmod).) You may wonder what happens if you write the name of a function-like macro without an arglist. With an ordinary function, this would turn into a pointer to the function, but macros don't have code for a pointer to point to the way ordinary functions do. In this circumstance, the macro is not expanded. For example, if we do #define CHECK(x) if ((x) < 0) printf("%s failed!\n",#x) and then write n = CHECK; then the preprocessor does not do anything with CHECK and the compiler proper sees it unchanged, as if the CHECK macro were temporarily #undeffed for the duration of that reference. This is occasionally useful; for example, see the description, below, of stdio functions. If you write what looks like a call to a function-like macro, but provide too few arguments, or, for a non-varargs macro, too many arguments, that is an error and will normally provoke an error when you run the compiler. But note that an argument may contain zero tokens; with the above CHECK macro, for example, CHECK() is a perfectly good call to the macro. In this case, the resulting expansion if (() < 0) printf("%s failed!\n","") is an error, but it's a syntax error in the expanded text, not a preprocessor error. Finally, there is macro varargs. This is based on the desire to be able to write something like FAIL("length (%d) is negative",length); and have it turn into something like printf("Failure at line %d: length (%d) is negative\n",lno,length); This is easy enough if a case like the above is all you care about, but people want not only the above, but also cases like FAIL("dimensions %dx%d are too large", xdim, ydim); FAIL("%d * %d = %d is too small",size,items,size*items); to work - that is, the number of arguments is variable. The way to do this is to write a macro definition whose last (or only) formal argument is the special sequence ..., which consumes any number of arguments in the call and corresponds to the special identifier __VA_ARGS__ in the replacement: #define FAIL(msg,...) printf("Failure at line %d: " msg "\n", lno, __VA_ARGS__) This is not perfect; in particular, the above does not work for FAIL("can't find any configuration"); because that expands to printf("Failure at line %d: " "can't find any configuration" "\n", lno, ); which is a syntax error because of that last comma. Standard C - C99, at least - does not have any fix for that problem. Some compilers provide compiler-specific fixes for it. In the meantime, possibly the best alternative is to use non-preprocessor varargs. Except that I haven't talked about that. But, since this leads up to it and I'm otherwise done with the preprocessor, this is a good place to describe it. We've been using a varargs function (that's the usual term for it, from "iable ument") all along: printf. I remarked, above, that we would eventually see how to write such functions. Let's learn that. To do this, write a normal function definition, only add, after the last argument, a comma and the special token ..., as in int sum_of(int count, ...) { ... } (where, for a change, the ... in the function header line is actually part of the code, not notation for something I'm omitting from the example). The function can then be called with any number of arguments, provided that there are at least as many actual arguments as there are explicit formal arguments. If all you want to do is declare such a function and call it, you don't need anything more: int sum_of(int, ...); j = sum_of(4, 100, 101, 102, 849); But if you want to actually define the function, you need a way to get hold of the actual arguments corresponding to that ..., and, without any names for them, we currently know of no way to access them. To do this, you need to #include and then use the macros va_start, va_arg, and va_end, which provides definitions of, together with a variable of type va_list, as in this example, which takes a count and then that many ints, and returns their sum: int sum_of(int count, ...) { va_list ap; int sum; int i; sum = 0; va_start(ap,count); for (i=count;i>0;i--) { sum += va_arg(ap,int); } va_end(ap); } The va_list type encapsulates whatever magic the implementation needs to walk the argument list. va_start starts the traversal; it takes the name of the va_list variable and the name of the last argument before the ... - count, in this example. (I don't understand why C makes the programmer pass in the name of the last non-variable argument, when the compiler obviously knows which that argument is, but that's the way it is defined.) Then va_arg takes the va_list variable and a type name and returns the next argument, assuming it's of that type. (The function must be able to figure out the type of the next argument in order to fetch it.) Finally, va_end closes off the traversal. There is one caveat: the type passed to va_arg must be such that simply appending a * to it results in a pointer to that type. Most of the types we've seen fit this, but a few, such as pointers to functions, don't; to address them, use a typedef. For example, recall the CALLBACK type I gave as an example of a typedef typedef int (*CALLBACK)(void *, int, struct state *); There is no way to write this in a va_arg call without a typedef, because "int (*)(void *, int, struct state *) *" is not a valid anything. But, with the above typedef, we can do cb = va_arg(ap,CALLBACK); -> Exercise: Build a rudimentary version of printf, using putchar() for the underlying output. You have the tools to do %d and %s formats at this point. (%g might be doable, but at a minimum would be a lot harder, especially if you want to do high-quality binary-to-decimal conversion. There are various other formats and flags that can be specified; if you really want to stretch yourself, look up the documentation and try to add some of them to your implementation.) ---------------- And perhaps after that exercise is a good time to talk more about printf and related topics. We've been using such things all along, printf in particular, but, so far, the details have all been deferred until...well, until now. Conceptually, printf and related calls (such as scanf, putchar, etc) all belong to a package called "standard I/O", or "stdio" for short. (That's where the "stdio" in "#include " comes from.) They all operate on abstractions spoken of as "stream"s. (You may see occasional references to "STREAMS". Usually, those are something else, a technical thing in certain Unix variants. I've seen this summarized as "`streams' means something different when shouted".) A stream is represented in C as a pointer to a FILE, where FILE is a type defined as part of including . It is an example of what's called an `opaque' type. This means that someone using the package should not try to pry into the internals of the type. In some cases you can't, because the internals are not visible; in some cases the internals are visible but they are not part of the documented and supported interface, so using them is a recipe for assorted nasty surprises. In the case of FILE, this means that you should never do anything with a FILE * except via the stdio interfaces: you should never try to allocate a FILE yourself, for example, and meddling with the internals of a FILE is liable to break things - possibly blatantly, possibly subtly, possibly not at all. You may note that we've never done anything with either FILE or FILE * so far. This is because we haven't needed to; we've been doing input with scanf and getchar and output with printf and putchar, and none of those use explicit FILE *s. But they exist under the hood nonetheless. Specifically, there are three streams which are pre-created for you: stdin, the so-called standard input; stdout, the standard output; and stderr, the standard error. The intent is that most programs, especially simple programs that just read data, process it, and output results, should read input data from stdin, write normal output to stdout, and, if they have any errors to report, write error messages to stderr. (There are, of course, calls allowing you to read, create, and write other files.) On Unix variants, and other systems providing Unixish interfaces, there are generally ways to feed data to the standard input and take the data on the standard output and save it somewhere - this is spoken of as `redirecting' those streams. For example, using `sort' (a program which sorts text files) as an example, if you have input data in a file `in.text' and want to sort it and put the sorted data in a file `out.text', you might type sort < in.text > out.text Most other operating systems also have some way to feed a file as input to a program and save the output from a program, but the syntax for doing so may or may not be similar to the above. This is mostly a side note for the time being, though; it is relevant to C only in that a lot of C code wants to read from stdin and write to stdout and stderr, and in a lot of cases this makes no sense unless you understand the paradigm of input and output streams being designed for redirecting. When you call printf or putchar, the output is sent to stdout; when you call scanf or getchar, the input is taken from stdin. There are variants of those calls (specifically, fprintf, putc, fscanf, and getc, respectively) that allow you to specify what stream you want to send output to or get input from. The most common example of this is probably that you will see output - error messages, usually - directed to stderr, as with fprintf(stderr,...): if (size < MINSIZE) { fprintf(stderr,"Forcing size to be at least %d\n",MINSIZE); size = MINSIZE; } declares a lot of stuff - a manual count of the C99 document says 46 functions and 19 other identifiers - but some of them are used only rarely. The 19 non-function identifers are BUFSIZ L_tmpnam TMP_MAX size_t EOF NULL _IOFBF stderr FILE SEEK_CUR _IOLBF stdin FILENAME_MAX SEEK_END _IONBF stdout FOPEN_MAX SEEK_SET fpos_t and the functions are clearerr fputc gets scanf fscanf fclose fputs perror setbuf vprintf feof fread printf setvbuf vscanf ferror freopen putc snprintf vsnprintf fflush fseek putchar sprintf vsprintf fgetc fsetpos puts sscanf vsscanf fgetpos ftell remove tmpfile fgets fwrite rename tmpnam fopen getc rewind ungetc fprintf getchar scanf vfprintf I'm not going to describe them all here. Few programs need more than a handful of them (though _which_ handful depends on the program), and a few of them, such as gets(), are designed badly enough that they should never be used anyway. (This is not the fault of the C99 people, at least not in the case of gets(); it is a historical artifact. gets() existed and was effectively frozen before C and the associated software ecosystem matured to the point where anyone realized the relevant issues existed.) Some of the functions are typically implemented as preprocessor macros, though most of those have real function variants available; you can call the real functions either by using #undef on the macros or by suppressing macro expansion, as outlined above in the preprocessor discussion. For example, you could write c = (getchar)(); to call the real-function version of getchar instead of any possible macro version. (That wouldn't often be done; the only reasons I know of to do it are (1) if you suspect a bug in the macro or (2) to affect debugging, so you can, for example, set a breakpoint on getchar. A more plausible thing to do would be taking a pointer to it, as in switch (input_source) { case IN_STDIN: input_fn = &getchar; break; case IN_BUFFER: input_fn = &get_from_buffer; break; case IN_DUMMY: input_fn = &get_dummy; break; } which sets input_fn to a suitable function depending on input_source.) -> Exercise: Pick a few of the above functions and look up their documentation. How to do this is system-specific (for most Unix variants, you'd use the `man' command, as in `man ftell' to look up the documentation on ftell()). You'll eventually need to do this for something, so it's a good idea to know how to do it. If you're going to be writing much C, you also will probably need to know enough about most of the above to recognize what they're doing, at least in general terms, when you see them, and realize when you want to use them yourself. Notice I said `functions' in that exercise. The non-function identifiers are much less well documented, so I'll mention them briefly here, in approximately order of (my opinion of) decreasing usefulness. The most important are probably EOF, FILE, stderr, stdin, and stdout. I've mentioned all of these above, though not in much detail. EOF is returned by many of the functions on error; for example, if printf() encounters an error trying to write output, it returns EOF. (Calls that don't encounter errors return other things. printf(), for example, returns the number of characters of output it generated.) FILE is, as I said above, a type used (in its pointer form) to represent stdio data streams. Streams can be input, output, or both; using an input-only stream with an output call such as fprintf produces errors, as does using an output-only stream with an input call such as getc. If a stream is both input and output (none of the standard three streams are, at least normally, but streams you open yourself can be), you can use both input and output calls on it. But they are all represented with FILE *, whether input, output, or both. And stderr, stdin, and stdout are, as I outlined above, the three predefined standard streams for, respectively, error output, input, and normal output. NULL I talked about above. Various of the standard headers are specified to define NULL; stdio.h is one of them. Next in importance are probably SEEK_CUR, SEEK_END, and SEEK_SET, which are defined for use with fseek() (and get used with a few other functions, not part of , perhaps most notably lseek() on most Unix variants). size_t is another thing set up by various headers, among them. It is an implementation-specific unsigned integer type, the return type of sizeof(). I'm not sure why the C people decided that should define it. BUFSIZ, _IOFBF, _IOLBF, and _IONBF are used with setbuf and setvbuf. They are used only rarely. (They are also three more of the handful of identifiers specified by C99 that begin with _, as I mentioned when discussing _Bool.) FILENAME_MAX, FOPEN_MAX, and TMP_MAX are perhaps used even more rarely. FILENAME_MAX is the implementation's recommendation for the size of a buffer used to hold a file pathname; on systems where there is a reasonably small limit on such pathnames, it's that limit, while on systems with no practical limit it is the recommended size. FOPEN_MAX is "the minimum number of files that the implementation guarantees can be open simultaneously"; this is not really useful for anything. TMP_MAX is "the maximum number of unique file names that can be generated by the tmpnam function"; it too is not really useful for anything. (Most environments provide better temporary-file facilities, if nothing else.) fpos_t is used by fgetpos and fsetpos, which support moving around in streams. It is useful only if you are trying to write extremely portable code; if you are willing to accept a slight portability hit, there are other ways of seeking (ftell and fseek in stdio, other functions if you're doing non-stdio I/O) that are significantly easier to use. L_tmpnam is "the size needed for an array of char large enough to hold a temporary file name string generated by the tmpnam function". As I mentioned above, under TMP_MAX, tmpnam is not all that useful (unless you're aiming for maximum portability). ---------------- The next exercise I want to give you requires that I finally de-simplify a very old simplifcation. It's time to talk about command-line arguments. C has the concept of a command line, which takes the form of strings provided as arguments to the program at startup. When using an OS with a command line, such as most Unix variants, you can specify arguments to a program. For example, I mentioned, way back at the beginning, that you might use "cc -o hello hello.c" to compile a C program in hello.c into something you could run directly. If the cc command is written in C (which is usually the case), then the "-o hello hello.c" part turns into the comamnd-line arguments. These take the form of arguments to main(). Now, above, I've always described main() as taking no arguments and returning int. There are actually two calling signatures the standard permits for main(): int main(void); int main(int, char **); So far, we've used only the first. That one is appropriate when you don't expect any command-line arguments, or, more precisely, when you want to ignore any that are given. The second gives you the ability to access the command-line arguments, via the int and the char **. (On most Unix variants, there is a third, which adds a third argument, another char **. That's for direct access to the environment, something I haven't talked about yet.) The int gives you the argument count and the char ** the arguments themselves. If we call the arguments c and v for expository convenience (you can call them anything you like; argc and argv are perhaps the most canonical names), so that main() looks like int main(int c, char **v) { ... } then c is the argument count, v[0] is the command name, if available (or a zero-length string if not), v[c] is a nil pointer, and the other elements of v[], if any, hold the arguments. In the above "cc -o hello hello.c" example, c would be 4, v[0] would be "cc" (or possibly "/usr/bin/cc" or some such, depending on the system), v[1] would be "-o", v[2] "hello", v[3] "hello.c", and v[4] a nil pointer. (The standard says that the int argument "shall be nonnegative", leaving open the possibility that it can be zero and there are _no_ argument strings, just v[0] being nil. This is unlikely, impossible in many common environments, but if you want maximum portability, be sure to take the possibility into account.) -> Exercise: Write a program which takes a filename from the user, however such things work for the OS you're using (on the command line is a reasonable option, if your OS _has_ command-line arguments for C programs) and prints out its contents. You'll need to look up how to open files and read from them, if nothing else. I'm deliberately asking you to research this stuff yourself; learning how to read up on calls you need is an important, if secondary, skill you will likely need. To talk about my next subject, I need to bring back something I mentioned once, above: alignment. When I mentioned unsigned integers, I remarked that they had the same alignment requirements as their signed counterparts, but I didn't go into any detail about alignment and what kinds of requirements there can be on it. Every kind of object has a so-called alignment. The C99 description of alignment is "requirement that objects of a particular type be located on storage boundaries with addresses that are particular multiples of a byte address". The alignment required varies depending on the object type, the architecture, and potentially other things (such as the compiler in use or the mode the hardware is being run in). Some compilers have (compiler-specific) ways to increase or decrease an object's alignment; usually, increasing alignment permits better performance at the price of using more memory, while decreasing alignment impairs performance but saves memory. There are occasionally other reasons to care about alignment, such as conformance to an externally-defined storage layout, though they are usually better served by mechanisms other than compiler-specific fiddling with object alignment. Depending on the hardware and sometimes other things (eg, compiler flags), accessing an object at an incorrectly aligned address may work just fine, may work with reduced performance, may silently perform the access at a different address (typically, the address rounded down to the appropriate alignment boundary), may throw an error of some sort, or the like. The spec is notably under-specified in some respects. For example, it is not clear whether alignment for a type of size S automatically implies alignment for any other type of size <= S. (As a simple, albeit hypothetical, example, I could imagine a system which required 3-byte alignment for one type and 5-byte alignment for another, neither of which necessarily implies the other.) I don't know whether this is deliberate or not, and I'm not aware of any system which requires alignment on any boundary other than a power of two. Alignment requirements can lead to other issues. For example, suppose that int is 4 bytes and must be aligned on a 4-byte boundary, but char is only 1 byte and has no alignment requirements (which can be thought of as 1-byte alignment). (Such systems are actually moderately common at this writing.) Then, consider struct foo { char c; int i; }; struct foo v[4]; We then have an issue. In order for both v[0].i and v[1].i to be suitably aligned, there have to be 4 bytes between them (or at least some multiple of 4 bytes), but we have only one char specified. In such a case, the compiler will usually insert three otherwise unused bytes, so that sizeof(struct foo) is 8, not 5. These are typically called "shim" or "padding" bytes. In the above example, they would most likely occur after c and before i, but they could also occur before c, or even partly before and partly after. In theory, the compiler is free to insert padding bytes anywhere it finds convenient. Padding bytes' values are indeterminate; they are invisible when using the struct's members to access the struct, but they may be visible by other techniques (such as memcpy, which I haven't yet discussed), and they may or may not be changed by assigning to the struct, either via its members or as a whole. You may be wondering why the padding bytes couldn't be inserted after i. They could be, but that then raises a different issue. For the i members to be correctly aligned, struct foo would have to be aligned on addresses which are congruent to 3 modulo 4. This is not possible; the spec says that "[a]ll pointers to structure types shall have the same representation and alignment requirements as each other", something that is necessary for structs to work across separate compilation boundaries. (Also, see below about malloc() and alignment.) But the compiler could, if it felt like it, insert padding before c, between c and i, and also after i. (Presumably it wouldn't unless it made some significant difference on the architecture in question.) Now that I've described alignment a bit, it's now time to de-simplify another simplification. When I first wrote about malloc(), above, I noted that there are actually three routines that can allocate space, not just one. So, what are the other two? They are called calloc() and realloc(). calloc() is just like malloc() except for two things: (1) it takes two arguments, not just one, with the amount of memory allocated being the product of those two values, and (2) the returned memory, instead of containing undefined values, is set to all-bits-zero. calloc appears to be designed for allocating arrays of values; in theory, its first argument is the number of objects to allocate and the second is the size of each object. This appears to open the possibility of calloc()'s return value might be aligned correctly for an array of objects of the size specified but not for larger objects. For example, on a machine where short int has size 2 and alignment 2, but int has size 4 and alignment 4, it would be reasonable if calloc(4,2) might return 8 bytes aligned to only a 2-byte boundary, whereas calloc(2,4) would have to return 8 bytes aligned to a 4-byte boundary. That's not how they're defined, though. A (non-nil) pointer returned by malloc, calloc, or realloc "is suitably aligned so that it may be assigned to a pointer to any type of object and then used to access such an object or an array of such objects in the space allocated (until the space is explicitly deallocated)". realloc takes a pointer to an existing block and a size and changes the size of the block to the given size, preserving the blocks' contents up to the old size or the new size, whichever is smaller. You can also pass a nil pointer as the existing-block address, in which case realloc amounts to malloc. realloc might just change the size of the allocation or might move the block; you should never depend on which it will do. Conceptually, the old block is freed by realloc; some implementations can return the same pointer without doing anything more than updating their internal record of the size of the block, but that is an implementation optimization. All these allocation calls may fail. When they fail, they return a nil pointer; in the case of realloc(), a failure does not deallocate the old block. (Much code assumes that they cannot fail, letting the program explode if they do. For some purposes, this is reasonable; for others, not.) Provided the argument is a valid block pointer, there are no failure modes for free(). Passing zero to malloc (or the equivalent, such as calloc with zero for one of the arguments) may return a nil pointer or may return a non-nil pointer, but in no case is it valid to use the memory, if any, pointed to. Some OSes provide other calls which can allocate memory in various ways. In some cases, they can be deallocated with free(), but more often (eg, mmap() on most Unix variants) they are paired with other calls to deallocate the memory (eg, munmap() for mmap()). Those, when they exist, are OS-specific; check your OS documentation if you have occasion to care about them. This is why I remarked, above, when talking about alignment, that malloc makes it impossible to have an object type whose alignment requirement is not a simple multiple of some number - for example, a struct cannot require alignment to an address equal to 3 modulo 4. Aside from the above remarks, this is because there are no pointers that satisfy the alignment criterion I quoted above: no pointer can be suitable for both that struct and an object that requires simple 4-byte alignment. This would make malloc, which is not an optional part of the spec, unimplementable. Now that I've explained a little bit of alignment and struct layout, I can explain something I remarked on above. When describing the keyword-counting program, I remarked that using %s to print keyword names would probably work fine in practice, but for the wrong reason. The struct in question looks like struct kw { char text[10]; int count; }; and, on most systems, ints need 4-byte alignment. This will _usually_ cause the compiler to insert two padding bytes between text and count. Since keywords[] is statically allocated, most systems will leave those padding bytes initialized to zero. (This is not promised by C, but then, neither is the presence of those padding bytes to begin with.) And, if they are present and zero, then %s can run off the end of text[] and find the first of those padding bytes as the terminating '\0' when printing the keyword name. This is an example of how code can appear to work in testing but then break later: if the above were done on a machine with 4-byte ints that padded the struct as outlined, %s could appear to work fine. If the code were then moved to another machine, where int is only 2 bytes long - or is 4 bytes, but on hardware that doesn't need more than 2-byte alignment for it - the padding probably would not be there and printing text with %s would, for the longest keyword(s), run off the end of text[] and start treating the bytes making up count as if they were part of the string. Unless the first such byte happens to be zero, this will usually print a garbage byte, possibly multiple garbage bytes. In this case, in most tests, the first such byte often will be zero, because there's only one 10-byte keyword - _Imaginary - and it's a relatively uncommon one, so count is likely to be zero, and a zero integer _does_ have all its bytes zero. On a machine that stores a multi-byte integer with the most-significant byte first, the first byte of count will be zero unless count is relatively large (just how large depends on the system, typically either 256 or 16777216). I mentioned, when first discussing passing arguments to functions, that functions can take multiple input arguments but can return only one value. But I also said that you can get a very similar effect by returning a struct. Let's suppose we're working on two-dimensional geometry and want to convert polar coordinates to Cartesian coordinates. Conceptually, this would look something like function polar-to-cartesian input: r, theta return: x=r*cos(theta), y=r*sin(theta) But C doesn't let us return more than one thing. If we try to write return r*cos(theta), r*sin(theta); then that will be taken as an instance of the , operator, computing and throwing away r*cos(theta), then computing and returning r*sin(theta). Not what we want. One common way of handling this kind of thing is to "return" the result values via pointers: void p_to_c(float r, float theta, float *xp, float *yp) { *xp = r * cos(theta); *yp = r * sin(theta); } with the caller doing something like float x, y; ... p_to_c(r,theta,&x,&y); ... use x and/or y ... This is fine in many cases. But not always. The alternative technique I'm discussing here would instead do something like struct cart_pt { float x; float y; }; struct cart_pt p_to_c(float r, float theta) { struct cart_pt rv; rv.x = r * cos(theta); rv.y = r * sin(theta); return(rv); } with the caller then doing something like struct cart_pt cart; ... cart = p_to_c(r,theta); ... use cart.x and/or cart.y ... ---------------- Back when I wrote about declaring multiple variables in a single declaration, I wrote that you could sometimes declare variables of different types in a single declaration, but that the types had to be related in ways I didn't talk about at the time. We've now seen enough that I can elaborate on that a bit. In a declaration, there's what we might call a base type and there are potentially many variables declared based on it. Some of you may have tried to, for example, declare two pointers-to-int in the same declaration and trieed something like int * xp, yp; only to find it didn't work the way you expected. This is what's going on: the above declares xp to be pointer-to-int, but yp to be simple int, because the declaration of yp is lacking a *. When declaring multiple variables in a single declaration, they share only what I called, above, the base type. In this example, that's int. If we write int *a, b[10], (*c)(float); then this is the same as int *a; int b[10]; int (*c)(float); where only the "int" part (what I'm calling the base type) is shared. The base type is a fundamental type (such as "int"), or a struct or union type, such as "struct kw" or "union num", or a typedef-defined type name. It can also have what are called type qualifiers, such as const or volatile, that we haven't met yet, and what are called storage classes, such as extern or static. We saw static, above; there are others, with different meanings, which occur in the same place in the declaration: typedef, extern, auto, and register. typedef I discussed above. extern specifies that the declaration is just a declaration, not a definition, and that the definition is elsewhere. You're not likely to need it until you start doing separate compilation. auto is almost completely unnecessary (I would drop the "almost", but I'm not quite certain); it is an explicit declaration that an object has automatic storage duration, and, anywhere that it's valid, it is the default. register is two things: it is a hint that the coder would like acess to the object to be as fast as possible, and it renders the object (and its sub-objects, if any) unusable with the unary & operator (without nonportable hackery, there is no such thing as a pointer to an object with storage class register). Historically, register declarations actually prompted the compiler to put the object in a machine register, to the extent possible, which on most machines means they literally did not have addresses. These days, compilers usually do a better job of deciding what should be kept in a register than humans can; about all register serves as is a way of ensuring that you can't mistakenly use & on the object. (Using & on an object renders a number of useful optimizations invalid in many circumstances.) There is also inline, which behaves syntactically much like a storage-class specifier (extern, etc) but is valid only when declaring a function. See the appendix for a discussion of inline functions. Following the base type, including storage-class specifiers if any, comes the declarator, which consists of parts potentially before the identifier (*) and parts potentially after the identifier ([...] for an array or (...) for a function). You may need parens, as I've mentioned above, especially if you're declaring a pointer-to-function or a pointer-to-array. But, once one identifier is declared, the next identifier, if any, starts over with a whole new declarator, reusing only the base type. There are also type qualifiers. There are three of them - const, volatile, and restrict - but you are unlikely to care about restrict until you've been working in C for quite a while. Type qualifiers can be applied to (almost?) any type, not just the base type of a declaration. You do this by specifying the relevant keyword (eg, const) at the point appropriate for the type. Deferring, for the moment, the question of what the qualifiers mean and thus whether any of these do anything useful, here are a few examples: const int x; x is a const int. const int *y; y is a non-const pointer to a const int. int * const z; z is a const pointer to a non-const int. const int * const xyz; xyz is a const pointer to a const int. const int * * const * abc; abc is a non-const pointer to a const pointer to a non-const pointer to a const int. volatile and restrict are parallel. Every object type has eight forms, qualified by any of the eight possible combinations of const or non-const, volatile or non-volatile, and restrict or non-restrict. The semantics of const are perhaps the easiest to explain: modifying an object that has const-qualified type is not permitted. Attempting to do so via an lvalue with const-qualified type is an error; attempting to do so via an lvalue with non-const-qualfied type produces undefined behaviour. If we write const int ci; const int *cip; int *ip; cip = &ci; ip = (int *)&ci; that's all fine. But *cip = 1; is an error (the lvalue *cip has const-qualified type), and *ip = 1; produces undefined behaviour (the lvalue *ip has unqualified type, but refers to an object - ci - defined to have const-qualified type). Many compilers have the ability to turn on warnings when a qualifier gets discarded from a pointer type, as in the "ip = (int *)&ci;" line above (which converts a `const int *' into a plain `int *'). These can help avoid such mistakes, but only if you've been very careful about declaring things const when - and only when - they need to be. Such compilers typically also have an option that makes string literals, strings inside " ", have type array-of-const-char rather than array-of-char; since modifying such a string produces undefined behaviour, this is appropriate in many contexts. This sort of checking is typically called `const poisoning'. volatile, while syntactically entirely parallel to const, has completely different semantics. Loosely put, a volatile object must be read and written exactly as often as, and when, the code appears to call for. (This is formalized in the spec.) The compiler is not allowed to, for example, cache the value in a register. It is typically used for values referring to hardware registers, or values shared between threads (we haven't discussed threads), or values used by both signal handlers and the code they interrupt (we haven't discussed signal handlers either). This is admittedly getting into very system-specific aspects; I mention them here not because I'm going to talk about hardware registers, or even signal handlers, in detail, but rather because I want to explain what volatile is for. For example, if we write volatile int *vip; int *ip; and somehow set vip and ip to point to ints, then *vip = 1; *vip = 1; *vip = 1; must result in three writes to the int pointed to by vip, whereas *ip = 1; *ip = 1; *ip = 1; may result in just one write (or even none, if, for example, the compiler can prove that *ip is already 1 before that sequence). restrict-qualified types exist to make certain optimizations possible. But you're not likely to need restrict until you've been working in C for some time, and probably not even then, so I'm deferring discussion of restrict to the appendix. ---------------- And it's finally time to tackle some of the pieces of stdio I've been putting off. I've mentioned, in a few places above, that stdio functions like printf and getchar return things that would otherwise be unnecessary to deal with error handling. The basic issue is that anytime the program interacts with the world outside itself, errors can potentially crop up. Disks fill up. Network connections break. Programs get fed data they're not expecting. So, almost all the stdio functions have error returns. Consider, for example, printf. In the absence of errors, it returns the number of bytes it prints (which can, depending on the format and arguments, be as low as zero, with no practical maximum). On error, it returns a negative value. scanf normally returns the number of input items it scanned and stored through arguments. What it returns in case of error depends on the kind of error and, in some cases, on how much progress it's made - check your system's docuemntation for full details; the C99 spec says that it returns the value of the macro EOF if an input failure occurs before any conversion. Otherwise, the function returns the number of input items assigned, which can be fewer than provided for, or even zero, in the event of an early matching failure. For example, if you scanf with "%d%d" and the input is "99balloons", the first %d will match the 99 and the second %d will try to consume tbe b, fail, and scanf will return 1, indicating that only one conversion succeeded. But if you scanf with "%d%d" and input is coming from a file which gets a read error when scanf first tries to read from it, you should get EOF. As an example of the "or even zero" case, consider trying to scan "Got %d lines" when the input is "Never got anything". The "G" in the format will be matched against the "N" in the input, fail, and provoke a zero return. That's why printf returns an int instead of void - so there's an error-reporting channel. scanf has a direct use for its return value, returning the number of conversions, but it's overloaded with an otherwise impossible value (EOF is required to be negative) to indicate errors. Similarly, getchar() returns an int, not a char, in order that it be able to signal errors: it returns the input byte, converted to unsigned char and then int, for normal input, whereas it returns EOF (which as mentioned above is negative) on error or EOF. You might expect putchar() to return nothing - and, above, that's how I've treated it. But it actually returns an int. (On success, it returns the character written, as an unsigned char converted to int, and therefore non-negative; on error, it returns EOF.) On a different note, stdio can be used with streams other than standard input and standard output. As mentioned briefly, above, the commonest use is probably use of fprintf with stderr to generate error messages, but they are not restricted to that. There are calls to open and close files, allowing you to manage streams you can read from and write to. For example, suppose we want to write a program to convert all uppercase to lowercase. This is a somewhat dodgy example, if only because `uppercase' is a fuzzy concept in an internationalized world; for that matter, so is mapping uppercase to lowercase. But for the sake of this example, let's pretend the whole world uses ASCII, so we have only 26 uppercase and 26 lowercase letters to deal with. I'm trying to illustrate file I/O, not internationalization, here. At the very simplest, we could use fixed file names - convert.in and convert.out, let us say - and write something like int main(void); int main(void) { FILE *in; FILE *out; int c; in = fopen("convert.in","r"); out = fopen("convert.out","w"); while (1) { c = getc(in); if (c == EOF) break; switch (c) { case 'A': c = 'a'; break; case 'B': c = 'b'; break; ... case 'Z': c = 'z'; break; } putc(c,out) } fclose(in); fclose(out); return(0); } This has various problems, even aside from the assumption mentioned above that only the ASCII letters need dealing with. It uses six stdio calls (fopen twice, fclose twice, and getc and putc once each), each of which can fail and none of which we handle errors from. But it does give something of the flavour of using stdio with files other than stdin, stdout, and stderr. (As a side note, neither of the arguments to fopen *has* to be a string literal; they can be, and at least the first one often is, computed at run time.) For full details on these functions, check your system's documentation. The C99 spec does describe them, but I don't want to quote it all here; it's full of detailed technical language, most of which likely won't mean anything to you at the moment and most of the details are not important right now. If you end up using C for reading and writing files much, read up on these functions. There are also variants of printf/scanf that do I/O to strings, rather than anything external; read up on sprintf (or preferably snprintf) and sscanf. printf/fprintf and scanf/fscanf can do a lot more than I've shown them doing. Check the documentation on your system; depending on which variant of C your system adheres to and what extensions it supports, the details of what you have available will vary - and I would like you to get used to using your system's documentation, because sooner or later you'll run into something I've never heard of and you'll need to know how to read up on it. (This tactic of mine does have a downside, in that many systems do not document the distinction between what they support and what C promises the existence of support for. But the things you can count on in practice is wider than what C promises support for, unless you're working on something for which you want really maximum portability - and in that case, you will have to restrict yourself to _less_ than the C spec promises, because not all systems adhere to the same version of the C spec, and even those that try to not infrequently fall short in various respects.) ---------------- It's time to tie up a bunch of loose ends. As a result, I'm going to be hopping around from subject to subject in what may superficially appear to be a rather disjointed way. I'll use section markers to indicate subject hops, leading to a bunch of very short sections. ---------------- I described unary &, above, for creating pointers. It can be applied to variables, but also to some other things, such as struct elements or array members. So, what is the actual requirement? It is that & can be applied to any lvalue that is neither bitfield nor declared register, to a function, or to the result of a pointer-follow (including subscripting). To quote C99, The operand of the unary & operator shall be either a function designator, the result of a [] or unary * operator, or an lvalue that designates an object that is not a bit- field and is not declared with the register storage-class specifier. Related to this is a hazard. As the above implies, & can in general be applied to objects with automatic storage duration (and doing so is too common and too useful to forbid). But what if the relevant block ends, and thus the object disappears, before the pointer does? (For example, maybe the pointer gets stored in a global variable.) You're then left with what I've seen called a dangling pointer: a pointer that doesn't point anywhere useful or correct. If this happens, following the pointer leads to undefined behaviour. Typically, the pointer will point into stack memory and will access whatever happens to be currently using that memory, if anything. But none of that is guaranteed by C. The best you can hope for is an immediate crash, but more often you'll read (or, worse, write) something unrelated. ---------------- I mentioned that I know of two or three uses for an always-false condition. Let's outline those. There's the do-while. Because it's bottom-tested, a do-while with a constant false condition executes just once, much like the same thing without the "do" or "while (0);". I know of two reasons to use such a thing. One is that a break statement can be used to skip the rest of the body in a well-controlled way, without needing to use a goto, which, as I wrote above, I recommend you avoid. (So can a continue statement, which can be useful from within a switch statement.) This is in contrast to an ordinary compound statement, which is what you get without the do and while(0); break and continue ignore those, looking for a containing statement to affect. The other arises in conjunction with macros. If you write a macro that does something which would normally need multiple statements, such as #define PROCESS(elt) update_values(elt); if (elt->len == 0) get_content(elt); then you have a potential problem. Because a call to it looks like a function call, it looks as though you should be able to append a ; to make it a statement: this = pop_list(); next = this->sibling; PROCESS(next); restore(next); This works fine, in this case; it turns into this = pop_list(); next = this->sibling; update_values(next); if (next->len == 0) get_content(next);; restore(next); which is fine; the trailing semicolon is just a harmless null statement. But if you try to treat it as a single statement, as in if (needs_process) PROCESS(e); (because PROCESS(e); looks like a single statement), you get if (needs_process) update_values(e); if (e->len == 0) get_content(e);; which is equivalent to if (needs_process) update_values(e); if (e->len == 0) get_content(e);; which is not what it looks as though the original code is doing. The usual way to address this is to instead make the #define read #define PROCESS(elt) do { update_values(elt); if (elt->len == 0) get_content(elt); } while (0) (notice the lack of a trailing semicolon), which makes PROCESS(e); - with the semicolon - exactly one statement, so it behaves properly when used in contexts like the above. Without the do and while(0), it would still work in a case like the above; #define PROCESS(elt) { update_values(elt); if (elt->len == 0) get_content(elt); } if (needs_process) PROCESS(e); turns into if (needs_process) { update_values(e); if (e->len == 0) get_content(e); }; which means if (needs_process) { update_values(e); if (e->len == 0) get_content(e); } ; which is fine. What's not fine is what happens if there's an else clause on that if: #define PROCESS(elt) { update_values(elt); if (elt->len == 0) get_content(elt); } if (needs_process) PROCESS(e); else mark_as_skipped(e); This becomes if (needs_process) { update_values(e); if (e->len == 0) get_content(e); }; else mark_as_skipped(e); which is a syntax error. The null-statement ; after the } forces the if statement to be complete at that point, so the else has nothing it can attach to. Using the do and while(0) neatly addresses this. The other is if (0). This can be useful in conjunction with any form of control transfer to a label. The most obvious form is a goto ... if (0) { retry: ...reset stuff... } ... if (...) goto retry; but it can also be useful in conjunction with a switch, when there are many cases but you want to share most of the code for a few of them: switch (mode) { case MODE_ONE: text = "one"; count = 1; if (0) { case MODE_TWO: text = "two"; count = 2; } if (0) { case MODE_THREE: text = "three"; count = 3; } ...much code, using text and count... break; ...many other cases... } In some cases, the "much code" can be pulled out into a separate function, but sometimes that is awkward. Which way is best in any particular case is a judgement call. I said "two or three" because you can consider the two uses for do-while(0) as either one or two. ---------------- When discussing arrays, I wrote that array sizes could be any integer constant expression, with a note that under some circumstances this could be relaxed somewhat. In the case of an array which is an ordinary variable with block scope (or function prototype scope; see the appendix) and no linkage (see below), the expression giving the size may be a non-constant expression; in this case, the array size can be any integer expression with value greater than zero. The array's size is determined when its declaration is reached and the array does not change size for as long as it exists, which is "until control flow leaves the scope of the declaration", in the words of C99. This appears to say that calling out to a function destroys the array; I suspect this is a bug in the wording, that it should be more like the description of automatic storage duration, where calling out to a function "suspends, but does not end" execution of the relevant section of code. ---------------- Related to variable-length arrays, some of the more recent versions of C support declarations inline amid executable code, as in printf("count is %d\n",n); struct element *list; list = build_list(); I don't like doing this, but that may be just because I learnt C before this facility was introduced. Such a variable is just like a variable declared at the top of the smallest enclosing block, except that its scope does not include the code in the block before its declaration, and its initializer, if any, runs at the time execution reaches its declaration. (The variable actually exists from the time the block is entered, though I have trouble thinking of any respect in which that makes any difference. The initialization, if any, is performed each time control flow reaches the declaration.) ---------------- When I was discussing identifier scope, I described file scope, block scope, and function scope, and noted there was a fourth scope, prototype scope. Prototype scope is necessary to deal with identifier definitions embedded in function declarations. (For historical reasons, mostly related to old-style function definitions, function declarations-without-definitions as I've described them are often called "prototypes".) You may recall that I wrote, when I was discussing function declarations, that you can skip the formal argument identifiers in a non-definition declaration, as in writing float size(int, float, int); rather than float trail_size(int inx, float slop, int ifnotfound); But we can also write the version with the names. What scope do those names (eg, "slop" in the above) have? As you have likely guessed by now, they have prototype scope. Prototype scope ends at the end of the function declarator in question. The above declaration for the name "slop" ends at the end of the function declaration. For formal argument declarations, that's what we want; there is no use for that declaration surviving. But prototype scope also applies to other identifiers declared within function prototypes, such as the struct tag in int do_stat(int stat, struct status *info); when there is no previous declaration of `struct status'. At least one compiler is capable of warning about this sort of declaration for struct types. While such declarations are useful and common when the struct types (struct status in this example) are already declared, they are not useful otherwise, because the struct status declared this way does not bear any similarity, except by accident of the tag being spelled the same, to any structs declared anywhere else. This then declares do_stat as taking a pointer to a type that cannot match any other type in the program, which is not a useful thing to do. ---------------- It's time to talk about the details of argument passing, and old-style function definitions (and declarations). I mentioned old-style arglists above, briefly. They are historical artifact. When C was first designed, they were the only form of function declaration or definition there was, but, with wide use, some of the problems they had became apparent, so the current scheme was designed. I'll start by going into a little more detail on how argument passing works, both because that's important in some cases and because one of the biggest differences between prototype-style and old-style function argument lists is how argument passing works. First, let's describe how argument passing works in modern C. There are two kinds of modern (prototype-style) argument list: the varargs kind (in which the declaration ends with ...) and non-varargs (without the ...). For all arguments in a non-varargs list, and for the explicit arguments in a varargs list, each actual argument is converted to the type of the corresponding formal argument, as if by assignment. That is, if we have an argument list containing SOMETYPE arg and we write somevalue in the corresponding place in the actual argument list when calling it, then somevalue is converted to SOMETYPE as if for SOMETYPE arg; arg = somevalue; The value, after conversion, is the value of that argument when the function is entered. For variable arguments in a varargs list (those after the last explicit argument), what C99 calls the "default argument promotions" are performed on each argument. This means that - For arguments of integer type smaller than int (strictly speaking, types with conversion rank less than that of int, conversion rank being a formalization of the notion of "smaller"), if an int can represent all values of the original type, the value is converted to int, otherwise it is converted to unsigned int. - Arguments of type float are converted to double. - All other types are unchanged. The resulting type, for each argument, must match the type the function uses to fetch the argument (ie, the type passed to va_arg). In passing, this is actually a serious failing of C99. It means that, for an implementation-defined integral type that might or might not be larger than int (an example is ino_t on most Unix variants), there is no correct way to pass it as a variable argument and receive it. If it is smaller than int, the called function has to specify int or unsigned int; otherwise, the called function has to specify the actual type - and there's no way to tell which of those is correct on a particular system. The caller can cast it, of course, but it is difficult to tell what to cast it to. The only real alternative is to pass and receive it as intmax_t or uintmax_t, but even then, without knowing whether the type is unsigned, there's no way to tell which to use. (Fortunately, this is rarely a problem in practice; I think it's come up all of once for me, in decades of using C as my primary language.) Okay, with that in mind, for functions with old-style definitions and declarations, the major difference can be phrased simply: arguments are passed, by the caller, as if every argument were a variable argument; the resulting arguments have to match, in number and type, the formal arguments specified in the function definition. ("Match", here, means what the standard formalizes as having "compatible" types.) There are also rules for the cases where the caller has a prototype in scope but the definition is old-style, or where the caller has no prototype in scope and the definition is modern. Loosely put, everything has to match up. See the standard for full details. This is why a modern-style function with no arguments has a "void" between the parens instead of nothing at all there: having nothing there was already taken by the old-style syntax, and the semantics are different enough that the C standardization committee decided (correctly, in my opinion) that a distinction was needed. ---------------- ================ Constructor expressions ================ Wide chars ================ The usual arithmetic conversions ================ For loops with declarations ================ Environment ================ Separate compilation linkage def/ref vs common extern why skip formals in function declarations ================ Library string support ================ Appendix: loose ends - complex types - trigraphs - inline functions - details of macro expansion - # and ## and zero-token arguments - restrict-qualified types - main() and hosted vs freestanding - backslash-newline line splicing Simplifications: - Details of printf. - Data types in full generality. - All ways variables can get values. - Places function calls aren't allowed in expressions. - Trap representations and stack trash (cf first debugging example). - Rules for incompletely braced initializers. - Other operators: (){} - "The usual arithmetic conversions". - for loops with declarations. - Variable-length arrays and automatic storage duration. - Linkage of identifiers. - extern declarations. - wide strings and wchar_t. - bitfields vs sizeof. - why skip formals in function declarations.