1. Introduction.
C is a computer language available on the GCOS and UNIXoperating systems at Murray Hill and (in preliminary form)on OS/360 at Holmdel. C lets you write your programsclearly and simply _ it has decent control flow facilitiesso your code can be read straight down the page, withoutlabels or GOTO's; it lets you write code that is compactwithout being too cryptic; it encourages modularity and goodprogram organization; and it provides good data-structuringfacilities.
This memorandum is a tutorial to make learning C aspainless as possible. The first part concentrates on thecentral features of C; the second part discusses those partsof the language which are useful (usually for getting moreefficient and smaller code) but which are not necessary forthe new user. This is "not" a reference manual. Details andspecial cases will be skipped ruthlessly, and no attemptwill be made to cover every language feature. The order ofpresentation is hopefully pedagogical instead of logical.Users who would like the full story should consult the 'CReference Manual' by D. M. Ritchie [1], which should be readfor details anyway. Runtime support is described in [2] and[3]; you will have to read one of these to learn how to com-pile and run a C program.
We will assume that you are familiar with the mysteriesof creating files, text editing, and the like in the operat-ing system you run on, and that you have programmed in somelanguage before.
2. A Simple C Program
main( ) {printf("hello, world");}
A C program consists of one or more functions, whichare similar to the functions and subroutines of a Fortranprogram or the procedures of PL/I, and perhaps some externaldata definitions. main is such a function, and in fact allC programs must have a main. Execution of the programbegins at the first statement of main. main will usuallyinvoke other functions to perform its job, some coming fromthe same program, and others from libraries.
One method of communicating data between functions isby arguments. The parentheses following the function namesurround the argument list; here main is a function of noarguments, indicated by ( ). The {} enclose the statementsof the function. Individual statements end with a semicolonbut are otherwise free-format.
printf is a library function which will format andprint output on the terminal (unless some other destinationis specified). In this case it prints
hello, world
A function is invoked by naming it, followed by a list ofarguments in parentheses. There is no CALL statement as inFortran or PL/I.
3. A Working C Program; Variables; Types and Type Declarations
Here's a bigger program that adds three integers andprints their sum.
main( ) {int a, b, c, sum;a = 1; b = 2; c = 3;sum = a + b + c;printf("sum is %d", sum);}
Arithmetic and the assignment statements are much thesame as in Fortran (except for the semicolons) or PL/I. Theformat of C programs is quite free. We can put severalstatements on a line if we want, or we can split a statementamong several lines if it seems desirable. The split may bebetween any of the operators or variables, but NOT in themiddle of a name or operator. As a matter of style, spaces,tabs, and newlines should be used freely to enhance reada-bility.
C has four fundamental types of variables:
int integer (PDP-11: 16 bits; H6070: 36 bits; IBM360: 32 bits)char one byte character (PDP-11, IBM360: 8 bits; H6070: 9 bits)float single-precision floating pointdouble double-precision floating point
There are also arrays and structures of these basic types,pointers to them and functions that return them, all ofwhich we will meet shortly.
All variables in a C program must be declared, althoughthis can sometimes be done implicitly by context. Declara-tions must precede executable statements. The declaration
int a, b, c, sum;
declares a, b, c, and sum to be integers.
Variable names have one to eight characters, chosenfrom A-Z, a-z, 0-9, and _, and start with a non-digit.Stylistically, it's much better to use only a single caseand give functions and external variables names that areunique in the first six characters. (Function and externalvariable names are used by various assemblers, some of whichare limited in the size and case of identifiers they canhandle.) Furthermore, keywords and library functions mayonly be recognized in one case.
4. Constants
We have already seen decimal integer constants in theprevious example _ 1, 2, and 3. Since C is often used forsystem programming and bit-manipulation, octal numbers arean important part of the language. In C, any number thatbegins with 0 (zero!) is an octal integer (and hence can'thave any 8's or 9's in it). Thus 0777 is an octal constant,with decimal value 511.
A ``character'' is one byte (an inherently machine-dependent concept). Most often this is expressed as a characterconstant, which is one character enclosed in singlequotes. However, it may be any quantity that fits in abyte, as in flags below:
char quest, newline, flags;quest = '?';newline = '\n';flags = 077;
The sequence `\n' is C notation for ``newline charac-ter'', which, when printed, skips the terminal to the begin-ning of the next line. Notice that `\n' represents only asingle character. There are several other ``escapes'' like`\n' for representing hard-to-get or invisible characters,such as `\t' for tab, `\b' for backspace, `\0' for end offile, and `\\' for the backslash itself.
float and double constants are discussed in section 26.
5. Simple I/O _ getchar, putchar, printf
main( ) {char c;c = getchar( );putchar©;}
getchar and putchar are the basic I/O library functionsin C. getchar fetches one character from the standard input(usually the terminal) each time it is called, and returnsthat character as the value of the function. When itreaches the end of whatever file it is reading, thereafterit returns the character represented by `\0' (ascii NUL,which has value zero). We will see how to use this veryshortly.
putchar puts one character out on the standard output(usually the terminal) each time it is called. So the pro-gram above reads one character and writes it back out. Byitself, this isn't very interesting, but observe that if weput a loop around this, and add a test for end of file, wehave a complete program for copying one file to another.
printf is a more complicated function for producingformatted output. We will talk about only the simplest useof it. Basically, printf uses its first argument as format-ting information, and any successive arguments as variablesto be output. Thus
printf ("hello, world\n");
is the simplest use _ the string ``hello, world\n'' isprinted out. No formatting information, no variables, sothe string is dumped out verbatim. The newline is necessaryto put this out on a line by itself. (The construction
"hello, world\n"
is really an array of chars. More about this shortly.)
More complicated, if sum is 6,
printf ("sum is %d\n", sum);
prints
sum is 6
Within the first argument of printf, the characters ``%d''signify that the next argument in the argument list is to beprinted as a base 10 number.
Other useful formatting commands are ``%c'' to printout a single character, ``%s'' to print out an entirestring, and ``%o'' to print a number as octal instead ofdecimal (no leading zero). For example,
n = 511;printf ("What is the value of %d in octal?", n);printf (" %s! %d decimal is %o octal\n", "Right", n, n);
prints
What is the value of 511 in octal? Right! 511 decimalis 777 octal
Notice that there is no newline at the end of the first out-put line. Successive calls to printf (and/or putchar, forthat matter) simply put out characters. No newlines areprinted unless you ask for them. Similarly, on input, char-acters are read one at a time as you ask for them. Eachline is generally terminated by a newline (\n), but there isotherwise no concept of record.
6. If; relational operators; compound statements
The basic conditional-testing statement in C is the ifstatement:
c = getchar( );if( c == '?' )printf("why did you type a question mark?\n");
The simplest form of if is
if (expression) statement
The condition to be tested is any expression enclosedin parentheses. It is followed by a statement. The expres-sion is evaluated, and if its value is non-zero, the state-ment is executed. There's an optional else clause, to bedescribed soon.
The character sequence `==' is one of the relationaloperators in C; here is the complete set:
== equal to (.EQ. to Fortraners)!= not equal to> greater than<>= greater than or equal to<= less than or equal to The value of ``expression relation expression'' is 1 ifthe relation is true, and 0 if false. Don't forget that theequality test is `=='; a single `=' causes an assignment,not a test, and invariably leads to disaster. Tests can be combined with the operators `&&' (AND),`' (OR), and `!' (NOT). For example, we can test whethera character is blank or tab or newline with if( c==' ' c=='\t' c=='\n' ) ... C guarantees that `&&' and `' are evaluated left to right_ we shall soon see cases where this matters. One of the nice things about C is that the statementpart of an if can be made arbitrarily complicated by enclos-ing a set of statements in {}. As a simple example, supposewe want to ensure that a is bigger than b, as part of a sortroutine. The interchange of a and b takes three statementsin C, grouped together by {}: if (a < t =" a;a" b =" t;}" c="getchar(" c =" getchar(" x =" y" z =" 0;" c =" getchar(" x =" a%b;" c =" c" c="getchar(" x =" a;elsex" x="a." x =" (a" c="getchar(" let =" dig" other =" 0;while(" c="getchar(" n =" 0;while(" c="getchar(" c ="=" n="n+1" x =" ++k;" x =" k++;" n =" name[i+j]" n =" 0;while(" c="getchar(" length =" %d\n" n =" 0;while(" c="getchar(" c ="=" n =" 0;}else" i =" 0;while(" n =" 0;while(" i="0;" sum =" 0;for(" i="0;" sum =" sum" i="0;" j="0;">127 or<0) i="0;" c="getchar("> size c < c =" size;" nchar =" 0;...while(" c="getchar("> size c < c =" size;buf[c]++;nchar++;}return(nchar);" i =" 0;" b =" &a;" b =" &a;c" c =" *b'" y =" &x[0];y" y =" &x[0];y++;" n="0;" n="0;" n="0;" temp =" *x;*x" y =" temp;}" i="1;" aflag =" bflag" cflag =" 0;if("> 1 && argv[1][0] == '-' ) {for( i=1; (c=argv[1][i]) != '\0'; i++ )if( c=='a' )aflag++;else if( c=='b' )bflag++;else if( c=='c' )cflag++;elseprintf("%c?\n", c);--argc;++argv;}...
There are several things worth noticing about thiscode. First, there is a real need for the left-to-rightevaluation that && provides; we don't want to look atargv[1] unless we know it's there. Second, the statements
--argc;++argv;
let us march along the argument list by one position, so wecan skip over the flag argument as if it had never existed _the rest of the program is independent of whether or notthere was a flag argument. This only works because argv isa pointer which can be incremented.
19. The Switch Statement; Break; Continue
The switch statement can be used to replace the multi-way test we used in the last example. When the tests arelike this:
if( c == 'a' ) ...else if( c == 'b' ) ...else if( c == 'c' ) ...else ...
testing a value against a series of constants, the switchstatement is often clearer and usually gives better code.Use it like this:
switch( c ) {
case 'a':aflag++;break;case 'b':bflag++;break;case 'c':cflag++;break;default:printf("%c?\n", c);break;}
The case statements label the various actions we want;default gets done if none of the other cases are satisfied.(A default is optional; if it isn't there, and none of thecases match, you just fall out the bottom.)
The break statement in this example is new. It isthere because the cases are just labels, and after you doone of them, you fall through to the next unless you takesome explicit action to escape. This is a mixed blessing.On the positive side, you can have multiple cases on a sin-gle statement; we might want to allow both upper and lowercase 'a': case 'A': ...
case 'b': case 'B': ...etc.
But what if we just want to get out after doing case `a' ?We could get out of a case of the switch with a label and agoto, but this is really ugly. The break statement lets usexit without either goto or label.
switch( c ) {
case 'a':aflag++;break;case 'b':bflag++;break;...}/* the break statements get us here directly */
The break statement also works in for and while statements _it causes an immediate exit from the loop.
The continue statement works _o_n_l_y inside for's andwhile's; it causes the next iteration of the loop to bestarted. This means it goes to the increment part of thefor and the test part of the while. We could have used acontinue in our example to get on with the next iteration ofthe for, but it seems clearer to use break instead.
20. Structures
The main use of structures is to lump together collec-tions of disparate variable types, so they can convenientlybe treated as a unit. For example, if we were writing acompiler or assembler, we might need for each identifierinformation like its name (a character array), its sourceline number (an integer), some type information (a charac-ter, perhaps), and probably a usage count (another integer).
char id[10];int line;char type;int usage;
We can make a structure out of this quite easily. Wefirst tell C what the structure will look like, that is,what kinds of things it contains; after that we can actuallyreserve storage for it, either in the same statement orseparately. The simplest thing is to define it and allocatestorage all at once:
struct {char id[10];int line;char type;int usage;} sym;
This defines sym to be a structure with the specifiedshape; id, line, type and usage are members of the struc-ture. The way we refer to any particular member of thestructure is
structure-name . member
as in
sym.type = 077;if( sym.usage == 0 ) ...while( sym.id[j++] ) ...etc.
Although the names of structure members never stand alone,they still have to be unique _ there can't be another id orusage in some other structure.
So far we haven't gained much. The advantages ofstructures start to come when we have arrays of structures,or when we want to pass complicated data layouts betweenfunctions. Suppose we wanted to make a symbol table for upto 100 identifiers. We could extend our definitions like
char id[100][10];int line[100];char type[100];int usage[100];
but a structure lets us rearrange this spread-out informa-tion so all the data about a single identifer is collectedinto one lump:
struct {char id[10];int line;char type;int usage;} sym[100];
This makes sym an array of structures; each array elementhas the specified shape. Now we can refer to members as
sym[i].usage++; /* increment usage of i-th identifier */for( j=0; sym[i].id[j++] != '\0'; ) ...etc.
Thus to print a list of all identifiers that haven't beenused, together with their line number,
for( i=0; i if( sym[i].usage == 0 )printf("%d\t%s\n", sym[i].line, sym[i].id);
Suppose we now want to write a function lookup(name)which will tell us if name already exists in sym, by givingits index, or that it doesn't, by returning a -1. We can'tpass a structure to a function directly _ we have to eitherdefine it externally, or pass a pointer to it. Let's trythe first way first.
int nsym 0; /* current length of symbol table */
struct {char id[10];int line;char type;int usage;} sym[100]; /* symbol table */
main( ) {...if( (index = lookup(newname)) >= 0 )sym[index].usage++; /* already there ... */elseinstall(newname, newline, newtype);...}
lookup(s)char *s; {int i;extern struct {char id[10];int line;char type;int usage;} sym[ ];
for( i=0; i if( compar(s, sym[i].id) > 0 )return(i);return(-1);}
compar(s1,s2) /* return 1 if s1==s2, 0 otherwise */char *s1, *s2; {while( *s1++ == *s2 )if( *s2++ == '\0' )return(1);return(0);}
The declaration of the structure in lookup isn't needed ifthe external definition precedes its use in the same sourcefile, as we shall see in a moment.
Now what if we want to use pointers?
struct symtag {char id[10];int line;char type;int usage;} sym[100], *psym;
psym = &sym[0]; /* or p = sym; */
This makes psym a pointer to our kind of structure (the sym-bol table), then initializes it to point to the first ele-ment of sym.
Notice that we added something after the word struct: a``tag'' called symtag. This puts a name on our structuredefinition so we can refer to it later without repeating thedefinition. It's not necessary but useful. In fact wecould have said
struct symtag {... structure definition};
which wouldn't have assigned any storage at all, and thensaid
struct symtag sym[100];struct symtag *psym;
which would define the array and the pointer. This could becondensed further, to
struct symtag sym[100], *psym;
The way we actually refer to an member of a structureby a pointer is like this:
ptr -> structure-member
The symbol `->' means we're pointing at a member of a
C Tutorial - 27 -
structure; `->' is only used in that context. ptr is apointer to the (base of) a structure that contains thestructure member. The expression ptr->structure-memberrefers to the indicated member of the pointed-to structure.Thus we have constructions like:
psym->type = 1;psym->id[0] = 'a';
and so on.
For more complicated pointer expressions, it's wise touse parentheses to make it clear who goes with what. Forexample,
struct { int x, *y; } *p;p->x++ increments x++p->x so does this!(++p)->x increments p before getting x*p->y++ uses y as a pointer, then increments it*(p->y)++ so does this*(p++)->y uses y as a pointer, then increments p
The way to remember these is that ->, . (dot), ( ) and [ ]bind very tightly. An expression involving one of these istreated as a unit. p->x, a[i], y.x and f(b) are namesexactly as abc is.
If p is a pointer to a structure, any arithmetic on ptakes into account the acutal size of the structure. Forinstance, p++ increments p by the correct amount to get thenext element of the array of structures. But don't assumethat the size of a structure is the sum of the sizes of itsmembers _ because of alignments of different sized objects,there may be ``holes'' in a structure.
Enough theory. Here is the lookup example, this timewith pointers.
struct symtag {char id[10];int line;char type;int usage;} sym[100];
main( ) {struct symtag *lookup( );struct symtag *psym;...if( (psym = lookup(newname)) ) /* non-zero pointer */psym -> usage++; /* means already there */elseinstall(newname, newline, newtype);
C Tutorial - 28 -
...}
struct symtag *lookup(s)char *s; {struct symtag *p;for( p=sym; p < &sym[nsym]; p++ )if( compar(s, p->id) > 0)return(p);return(0);}
The function compar doesn't change: `p->id' refers to astring.
In main we test the pointer returned by lookup againstzero, relying on the fact that a pointer is by definitionnever zero when it really points at something. The otherpointer manipulations are trivial.
The only complexity is the set of lines like
struct symtag *lookup( );
This brings us to an area that we will treat only hurriedly_ the question of function types. So far, all of our func-tions have returned integers (or characters, which are muchthe same). What do we do when the function returns some-thing else, like a pointer to a structure? The rule is thatany function that doesn't return an int has to say expli-citly what it does return. The type information goes beforethe function name (which can make the name hard to see).Examples:
char f(a)int a; {...}
int *g( ) { ... }
struct symtag *lookup(s) char *s; { ... }
The function f returns a character, g returns a pointer toan integer, and lookup returns a pointer to a structure thatlooks like symtag. And if we're going to use one of thesefunctions, we have to make a declaration where we use it, aswe did in main above.
Notice th parallelism between the declarations
struct symtag *lookup( );struct symtag *psym;
C Tutorial - 29 -
In effect, this says that lookup( ) and psym are both usedthe same way _ as a pointer to a strcture _ even though oneis a variable and the other is a function.
21. Initialization of Variables
An external variable may be initialized at compile timeby following its name with an initializing value when it isdefined. The initializing value has to be something whosevalue is known at compile time, like a constant.
int x 0; /* "0" could be any constant */int a 'a';char flag 0177;int *p &y[1]; /* p now points to y[1] */
An external array can be initialized by following its namewith a list of initializations enclosed in braces:
int x[4] {0,1,2,3}; /* makes x[i] = i */int y[ ] {0,1,2,3}; /* makes y big enough for 4 values */char *msg "syntax error\n"; /* braces unnecessary here */char *keyword[ ]{"if","else","for","while","break","continue",0};
This last one is very useful _ it makes keyword an array ofpointers to character strings, with a zero at the end so wecan identify the last element easily. A simple lookup rou-tine could scan this until it either finds a match orencounters a zero keyword pointer:
lookup(str) /* search for str in keyword[ ] */char *str; {int i,j,r;for( i=0; keyword[i] != 0; i++) {for( j=0; (r=keyword[i][j]) == str[j] && r != '\0'; j++ );if( r == str[j] )return(i);}return(-1);}
Sorry _ neither local variables nor structures can beinitialized.
22. Scope Rules: Who Knows About What
A complete C program need not be compiled all at once;the source text of the program may be kept in several files,and previously compiled routines may be loaded fromlibraries. How do we arrange that data gets passed from oneroutine to another? We have already seen how to use func-tion arguments and values, so let us talk about externaldata. Warning: the words declaration and definition areused precisely in this section; don't treat them as the samething.
A major shortcut exists for making extern declarations.If the definition of a variable appears BEFORE its use insome function, no extern declaration is needed within thefunction. Thus, if a file contains
f1( ) { ... }
int foo;
f2( ) { ... foo = 1; ... }
f3( ) { ... if ( foo ) ... }
no declaration of foo is needed in either f2 or or f3,because the external definition of foo appears before them.But if f1 wants to use foo, it has to contain the declara-tion
f1( ) {extern int foo;...}
This is true also of any function that exists onanother file _ if it wants foo it has to use an externdeclaration for it. (If somewhere there is an externdeclaration for something, there must also eventually be anexternal definition of it, or you'll get an ``undefined sym-bol'' message.)
There are some hidden pitfalls in external declarationsand definitions if you use multiple source files. To avoidthem, first, define and initialize each external variableonly once in the entire set of files:
int foo 0;
You can get away with multiple external definitions on UNIX,but not on GCOS, so don't ask for trouble. Multiple ini-tializations are illegal everywhere. Second, at the begin-ning of any file that contains functions needing a variablewhose definition is in some other file, put in an externdeclaration, outside of any function:
extern int foo;
f1( ) { ... }etc.
The #include compiler control line, to be discussedshortly, lets you make a single copy of the externaldeclarations for a program and then stick them into each ofthe source files making up the program.
23. #define, #include
C provides a very limited macro facility. You can say
#define name something
and thereafter anywhere ``name'' appears as a token, ``some-thing'' will be substituted. This is particularly useful inparametering the sizes of arrays:
#define ARRAYSIZE 100int arr[ARRAYSIZE];...while( i++ < x =" x">> right shift (arithmetic on PDP-11; logical on H6070, IBM360)
25. Assignment Operators
An unusual feature of C is that the normal binaryoperators like `+', `-', etc. can be combined with theassignment operator `=' to form new assignment operators.For example,
x =- 10;
uses the assignment operator `=-' to decrement x by 10, and
x =& 0177
forms the AND of x and 0177. This convention is a usefulnotational shortcut, particularly if x is a complicatedexpression. The classic example is summing an array:
for( sum=i=0; i sum =+ array[i];
But the spaces around the operator are critical! Forx = -10;
sets x to -10, while
x =- 10;
subtracts 10 from x. When no space is present,
x=-10;
also decreases x by 10. This is quite contrary to theexperience of most programmers. In particular, watch outfor things like
c=*s++;y=&x[0];
both of which are almost certainly not what you wanted.Newer versions of various compilers are courteous enough towarn you about the ambiguity.
Because all other operators in an expression areevaluated before the assignment operator, the order ofevaluation should be watched carefully:
x = x< x ="<<" sum =" 0.0;for(" i="0;" sum ="+" avg =" sum/n;" m =" x;y" pi =" 3.14159;large" href="http://www.e247.com/eyelandreg.jsp">http://www.e247.com/eyelandreg.jspSend & receive large files with 100MB of storage
.ent and labels, so you can branchabout the way you used to. But most of the time goto'saren't needed. (How many have we used up to this point?)The code can almost always be more clearly expressed byfor/while, if/else, and compound statements.
One use of goto's with some legitimacy is in a programwhich contains a long loop, where a while(1) would be tooextended. Then you might write
mainloop:...goto mainloop;
Another use is to implement a break out of more than onelevel of for or while. goto's can only branch to labelswithin the same function.
28. Acknowledgements
I am indebted to a veritable host of readers who madevaluable criticisms on several drafts of this tutorial.They ranged in experience from complete beginners throughseveral implementors of C compilers to the C languagedesigner himself. Needless to say, this is a wide enoughspectrum of opinion that no one is satisfied (including me);comments and suggestions are still welcome, so that somefuture version might be improved.
References
C is an extension of B, which was designed by D. M.Ritchie and K. L. Thompson [4]. The C language design andUNIX implementation are the work of D. M. Ritchie. The GCOSversion was begun by A. Snyder and B. A. Barres, and com-pleted by S. C. Johnson and M. E. Lesk. The IBM version isprimarily due to T. G. Peterson, with the assistance of M.E. Lesk.
[1] D. M. Ritchie, C Reference Manual. Bell Labs, Jan.1974.
[2] M. E. Lesk & B. A. Barres, The GCOS C Library.Bell Labs, Jan. 1974.
[3] D. M. Ritchie & K. Thompson, UNIX Programmer'sManual. 5th Edition, Bell Labs, 1974.
[4] S. C. Johnson & B. W.
Subscribe to:
Post Comments (Atom)


No comments:
Post a Comment