C syntax
|
The syntax of the C programming language is a set of rules that defines how a C program will be written and interpreted.
Contents |
3.1 Compound statements |
Comments
Text starting with /*
is treated as a comment and ignored. The comment ends at the next */
and can span multiple lines. Accidental omission of the comment terminator is problematic in that the next comment's properly constructed comment terminator will be used to terminate the initial comment, and all code in between the comments will be considered as a comment.
The C99 standard introduced C++ style line comments. These start with //
and extend to the end of the line.
Functions
A C program consists of functions and variables. C functions are akin to the subroutines of Fortran or the procedures of Pascal.
An implementation of C providing all of the standard library functions is called a hosted implementation. Programs written for hosted implementations are required to define a special function called main
, which is the first function called when execution of the program begins. Here is a minimal C program:
int main (void) { return 0; }
The main
function will usually call other functions to help it perform its job.
Some implementations are not hosted, usually because they are not intended to be used with an operating system. Such implementations are called free-standing in the C standard. A free-standing implementation is free to specify how it handles program startup; in particular it need not require a program to define a main
function.
Functions may be written by the programmer or provided by existing libraries. The latter are declared by including header files—with the #include
preprocessing directive— and then linked into the final executable image. Certain library functions, such as printf
, are defined by the C standard; these are referred to as the standard library functions.
A function may return a value to the environment that called it. This is usually another C function, however the calling environment of the main
function is the higher-level process in Unix-like systems or the operating system itself in other cases. Typically, the return value zero of main
signifies successful completion when the program terminates. The printf
function mentioned above returns how many characters were printed, but this value is usually ignored.
A C function consists of a return type (void
if no value is returned), a unique name, a list of parameters in parentheses—void
or nil if there are none—and a function body delimited by curly braces. The syntax of the function body is equivalent to that of a compound statement.
Control structures
Basically, C is a free-form language.
Note: bracing style varies from programmer to programmer and can be the subject of great debate ("religious wars"). See Indent style for more details.
Compound statements
Compound statements in C have the form
{ <optional-declaration-list> <optional-statement-list> }
and are used as the body of a function or anywhere that a single statement is expected.
Expression statements
A statement of the form
<optional-expression> ;
is an expression statement. If the expression is missing, the statement is called a null statement.
Selection statements
C has three types of selection statements: two kinds of if
and the switch
statement.
The two kinds of if
statement are
if (<expression>) <statement>
and
if (<expression>) <statement> else <statement>
In the if
statement, if the expression in parentheses is nonzero or true, control passes to the statement following the if
. If the else
clause is present, control will pass to the statement following the else
clause if the expression in parentheses is zero or false. The two are disambiguated by matching an else
to the next previous unmatched if
at the same nesting level. Braces may be used to override this or for clarity.
The switch
statement causes control to be transferred to one of several statements depending on the value of an expression, which must have integral type. The substatement controlled by a switch is typically compound. Any statement within the substatement may be labeled with one or more case
labels, which consist of the keyword case
followed by a constant expression and then a colon (:).
No two of the case constants associated with the same switch may have the same value. There may be at most one default
label associated with a switch; control passes to the default
label if none of the case labels are equal to the expression in the parentheses following switch
.
Switches may be nested; a case
or default
label is associated with the smallest switch that contains it. Switch statements can "fall-through", that is, when one case section has completed its execution, statements will continue to be executed downward until a break statement is encountered. This may prove useful in certain circumstances, newer programming languages forbid case statements to "fall-through".
In the below example, if <label2> is reached, the statements <statements 2> are executed and nothing more inside the braces. However if <label1> is reached, both <statements 1> and <statements 2> are executed since there is no break
to separate the two case statements.
switch (<expression>) { case <label1> : <statements 1> case <label2> : <statements 2> break; default : <statements> }
Iteration statements
C has three forms of iteration statement:
do <statement> while (<expression>);
while (<expression>) <statement>
C89's for
loop
for (<expression> ; <expression> ; <expression>) <statement>
was generalized in C99 to
for (<declaration> <expression> ; <expression>) <statement>
In the while
and do
statements, the substatement is executed repeatedly so long as the value of the expression remains nonzero or true. With while
, the test, including all side effects from the expression, occurs before each execution of the statement; with do
, the test follows each iteration.
If all three expressions are present in a for
, the statement
for (e1; e2; e3) s;
is equivalent to
e1; while (e2) { s; e3; }
Any of the three expressions in the for
loop may be omitted. A missing second expression makes the while
test nonzero, creating an infinite loop.
Jump statements
Jump statements transfer control unconditionally. There are four types of jump statements in C: goto
, continue
, break
, and return
.
The goto
statement looks like this:
goto <identifier>;
The identifier must be a label located in the current function. Control transfers to the labeled statement.
A continue
statement may appear only within an iteration statement and causes control to pass to the loop-continuation portion of the smallest enclosing such statement. That is, within each of the statements
while (expression) { /* ... */ cont: ; }
do { /* ... */ cont: ; } while (expression);
for (optional-expr; optexp2; optexp3) { /* ... */ cont: ; }
a continue
not contained within a nested iteration statement is the same as goto cont
.
The break
statement is used to get out of a for
loop, while
loop, do
loop, or switch
statement. Control passes to the statement following the terminated statement.
A function returns to its caller by the return
statement. When return
is followed by an expression, the value is returned to the caller of the function. Flowing off the end of the function is equivalent to a return
with no expression. In either case, the returned value is undefined.
Operators
See the main article Operators in C and C++.
For reference here is the operator precedence table for C89:
Operator | Description | Associativity |
---|---|---|
()
| Parenthesis (grouping) Array subscript | left-to-right |
++ and --
| Prefix increment/decrement Unary plus/minus | right-to-left |
* , / , and %
| multiplication/division/modulus | left-to-right |
+ and -
| addition/subtraction | |
<< and >>
| bitwise left shift/right shift | |
< and <=
| relational less than/less than or equal to relational greater than/greater than or equal to | |
== and !=
| equal to/not equal to | |
&
| bitwise AND | |
^
| bitwise XOR (exclusive or) | |
|
| bitwise OR (inclusive or) | |
&&
| logical AND | |
||
| logical OR | |
?:
| ternary conditional | right-to-left |
=
| direct assignment additive/subtractive assignment | |
,
| comma operator | left-to-right |
Source: C Operator Precedence and Associativity (http://www.difranco.net/cop2220/op-prec.htm)
Data declaration
Primitive data types
Many programming languages—of whom C is one—represent numbers in two forms: integral and real (or non-integral). This distinction is made due to the technical aspect of the methods used to store values in memory.
The integral specifier is int
; it is used to denote the representation of integers. The integral type comes in different sizes, denoting the memory usage and highest magnitude[1]. Modifiers are used to specify sizes: short
, nil (none), long
, and long long
[2]. The character type, whose specifier is char
, represents a single 8-bit integral value; in practice, not all compilers however treat this type as an integer.
The real (non-integral) form is used to denote the representation of numbers with a decimal or rational component. They do not however accurately represent rational numbers; they are approximated instead. There are three types of real values, denoted by their specifier: single-precision (specifier double
), double-precision (long double
) and floating-point (float
). Each of these represent non-integral values in a different form.
Integral types can be either signed
(which is implied when not specified) or unsigned
. This also applies to the character type. When signed, the most significant bit used in the representation of the integer in memory is instead used to represent sign (positive or negative). For example, a signed 16-bit integer uses the most significant bit for its sign and the remaining 15 bits for the representation of its value. It follows that when unsigned, the most significant bit of an integer is not limited in this fashion.
[1] = In terms of integral values, magnitude represents the maximum value (independent of sign) that can be represented. Signing skews this range, as is illustrated in the table below.
[2] = The long long
modifier was introduced in the C99 standard.
Constants that define boundaries of primitive data types
The standard header file limits.h
defines the minimum and maximum values of the integral primitive data types, amongst other limits. The standard header file float.h
defines the minimum and maximum values of the float
, double
, and long double
. It also defines other limits that are relevant to the processing of floating-point, single-precision, and double-precision values as they are defined in the IEEE 754 standard.
Implicit Specifier | Explicit Specifier | Minimum Value | Maximum Value |
char
| signed char
| CHAR_MIN
| CHAR_MAX
|
unsigned char
| same | 0 | UCHAR_MAX
|
short
| signed short int
| SHRT_MIN
| SHRT_MAX
|
unsigned short
| unsigned short int
| 0 | USHRT_MAX
|
none, signed , or int
| signed int
| INT_MIN
| INT_MAX
|
unsigned
| unsigned int
| 0 | UINT_MAX
|
long
| signed long int
| LONG_MIN
| LONG_MAX
|
unsigned long
| unsigned long int
| 0 | ULONG_MAX
|
long long [1]
| signed long long int [1]
| LLONG_MIN [2]
| LLONG_MAX [2]
|
unsigned long long [1]
| unsigned long long int [1]
| 0 | ULLONG_MAX [2]
|
[1]—The long long
modifier is only supported by C99-compliant compilers.
[2]—The LLONG_MIN
, LLONG_MAX
, and ULLONG_MAX
constants are only defined in limits.h
if it was intended to be used with a C99-compliant compiler.
Typical boundaries of primitive integral types
The following is a list of the common integral types and their typical sizes and boundaries. These may vary from one implementation to another. ISO C provides the inttypes.h header, which defines signed and unsigned integral types of guaranteed sizes between 8 and 64 bits.
Implicit Specifier | Explicit Specifier | Bits | Bytes | Minimum Value | Maximum Value |
---|---|---|---|---|---|
char
| signed char
| 8 | 1 | -128 | 127 |
unsigned char
| same | 8 | 1 | 0 | 255 |
short
| signed short int
| 16 | 2 | -32 768 | 32 767 |
unsigned short
| unsigned short int
| 16 | 2 | 0 | 65 535 |
long
| signed long int
| 32 | 4 | -2 147 483 648 | 2 147 483 647 |
unsigned long
| unsigned long int
| 32 | 4 | 0 | 4 294 967 295 |
long long [1]
| signed long long int [1]
| 64 | 8 | -9 223 372 036 854 775 808 | 9 223 372 036 854 775 807 |
unsigned long long [1]
| unsigned long long int [1]
| 64 | 8 | 0 | 18 446 744 073 709 551 615 |
[1]—The long long
modifier is only supported by C99-compliant compilers.
The size and limits of the nil int
primitive type (without the short
, long
, or long long
modifiers) vary much more than the other integral types between implementations. It is becoming common to define it as a synonym of size_t
—which is defined in sys/types.h
—and therefore large enough to accommodate the largest possible pointer value that can be allocated by the system. The Single UNIX Specification indicates that the int
type must be at least 32 bits, however the ISO C standards only require 16 bits.
Reference types
The asterisk modifier (*
) specifies a reference type, which is more commonly known as a pointer. Where the specifier int
would refer to the primitive integral type, the specifier int *
refers to the reference integral type, a pointer type. Reference values associate two pieces of information: a memory address and a data type. The following line of code declares a reference integral variable (a pointer) called ptr
:
int *ptr;
Referencing
When a local pointer is declared, no address is associated with it. The address associated with a pointer can be changed using assignment. In the following example, ptr
will be set so that it points to the same data as the primitive integral variable a
:
int *ptr; int a; ptr = &a;
In order to accomplish this, the reference operator (unary &
) was used. It returns the memory location of the variable or storage entity that follows. The operator as a result is often called the "address-of" operator.
Dereferencing
By the same token, the value can be retrieved from a reference value. In the following example, the primitive integral variable b
will be set to the data that is referenced by ptr
:
int *ptr; int b; b = *ptr;
In order to accomplish that task, the dereference operator (unary *
) was used. It returns the primitive data to which its only parameter—which must be a reference value—points. The expression *ptr
is effectively a pseudonym for b
.
The overloading of the asterisk character with two related behaviors can be confusing at first. It is important to understand the differences between its use as a modifier in a declaration and as a unary operator in an expression.
Equivalent reference and primitive statements
The following is a table that lists the equivalent statements with both primitive and reference types, using both the reference and dereference operators. In it, the primitive variable d
and the reference variable ptr
are implied:
To a primitive value | To a reference value | |
---|---|---|
From a primitive value | d
| &d
|
From a reference value | *ptr
| ptr
|
Arrays
Static array declaration
Arrays are used in C to represent structures of consecutive values of the same type. The declaration of a static (fixed-size) array is contrary to other forms; consider the following syntax:
int array[n];
Which will define an array named array
to hold n
values of the primitive type int
. In practice, memory for n
integral values has been reserved and assigned for this array. The variable array
is actually of the reference integral type; it points to the memory address of the first value.
Accessing elements
The primary facility for accessing the component values of an array—which are often called elements—is the array subscript operators. To access the n
th element of array
, the calling syntax would be array[n]
, which would return the primitive value there associated. This appears very similar to—but is in function entirely different than—the declaration syntax.
Array subscripts begin numbering at 0. The largest logical array subscript is therefore equal to the number of elements in the array less 1. To illustrate this, consider an array of 10 elements; the first element would be [0]
and the last element would be [9]
.
It is also possible to use pointer arithmetic to specify the reference value for each of the array elements. The following table illustrates both methods for the existing array:
Element | 0 | 1 | 2 | n |
---|---|---|---|---|
Primitive | array[0]
| array[1]
| array[2]
| array[n]
|
Pointer | *array
| *(array + 1)
| *(array + 2)
| *(array + n)
|
Dynamic arrays
C provides no facility for bounds checking with arrays. Though logically the last subscript in an array of 10 elements would be 9, subscripts 10, 11, and so forth could be specified. Because arrays are homogeneous—that is they consist of only one type of data—only two pieces of information need be known: the address of the first element and the type of data.
Recall the declaration of a static array, which creates a reference integral variable and allocates the appropriate memory to it:
int array[n];
This behavior can be reproduced with the help of the C standard library. The calloc
function provides a simple method for allocating memory. It takes two parameters: the number of elements and the size of each element. Upon successful allocation, calloc
returns a pointer to the first element and initialises all of the elements to zero. If the allocation could not be completed, calloc
returns a null pointer. The following segment is therefore identical in function to the static declaration:
int *array; array = calloc(n, sizeof(int));
The result is still a reference integral variable (the pointer array
) that points to the first of n
elements, each of which are of the primitive integral type. The advantage in using this dynamic allocation is that the size of an array—the amount of memory that is safely allocated to it—can be changed after it has been declared.
When the dynamically-allocated memory is no longer needed, it should be released back to the operating system. This is done with a call to the free
function. It takes a single parameter: a pointer to previously allocated memory. Typically, this is the value that was returned by the call to calloc
. It is considered good practice to then set the pointer to NULL
so that further attempts to access the memory to which it points will fail.
free(array); array = NULL;
Multidimensional arrays
In addition, C supports arrays of multiple dimensions. The method for defining them would make it appear that they are arrays of arrays, however in practice this may not be the case. Consider the following syntax:
int array2d[rows][columns];
Which will define an array of two dimensions; its first dimension will contain rows
elements. Its second will contain rows * columns
elements—a set of columns
elements for each first-dimension element.
Regardless of the actual implementation, these multidimensional arrays can be treated as if they were arrays of pointers. For example, array2d[1]
(if rows
was ≥ 1) will be a reference integral value that points to an array of columns
elements.
Strings
Strings may be manipulated without using the standard library. However, the library contains many useful functions for working with both zero-terminated strings and unterminated arrays of char
.
The most commonly used string functions are:
-
strcat(dest, source)
- appends the stringsource
to the end of stringdest
-
strchr(s, c)
- finds the first instance of characterc
in strings
and returns a pointer to it or a null pointer ifc
is not found -
strcmp(a, b)
- compares stringsa
andb
(lexical ordering); returns negative ifa
is less thanb
, 0 if equal, positive if greater. -
strcpy(dest, source)
- copies the stringsource
to the stringdest
-
strlen(st)
- return the length of stringst
-
strncat(dest, source, n)
- appends a maximum ofn
characters from the stringsource
to the end of stringdest
; characters after the null terminator are not copied. -
strncmp(a, b, n)
- compares a maximum ofn
characters from stringsa
andb
(lexical ordering); returns negative ifa
is less thanb
, 0 if equal, positive if greater. -
strncpy(dest, source, n)
- copies a maximum ofn
characters from the stringsource
to the stringdest
-
strrchr(s, c)
- finds the last instance of characterc
in strings
and returns a pointer to it or a null pointer ifc
is not found
The less common string functions are:
-
strcoll(s1, s2)
- compare two strings according to a locale-specific collating sequence -
strcspn(s1, s2)
- returns the index of the first character ins1
that matches any character ins2
-
strerror(err)
- returns a string with an error message corresponding to the code inerr
-
strpbrk(s1, s2)
- returns a pointer to the first character ins1
that matches any character ins2
or a null pointer if not found -
strspn(s1, s2)
- returns the index of the first character ins1
that matches no character ins2
-
strstr(st, subst)
- returns a pointer to the first occurrence of the stringsubst
inst
or a null pointer if no such substring exists. -
strtok(s1, s2)
- returns a pointer to a token withins1
delimited by the characters ins2
. -
strxfrm(s1, s2, n)
- transformss2
intos1
using locale-specific rules
File I/O
In C, input and output are performed via a group of functions in the standard library. In ANSI/ISO C, those functions are defined in the <stdio.h>
header.
Standard I/O
Three standard I/O streams are predefined:
-
stdin
standard input -
stdout
standard output -
stderr
standard error
These streams are automatically opened and closed by the runtime environment, they need not and should not be opened explicitly.
The following example demonstrates how a filter program is typically structured:
#include <stdio.h> int main() { int c; while (( c = getchar()) != EOF ) { /* do various things to the characters */ if (anErrorOccurs) { fputs("an error eee occurred\n", stderr); break; } /* ... */ putchar(c); /* ... */ } return 0; }
Passing command line arguments
The parameters given on a command line are passed to a C program with two predefined variables - the count of the command line arguments in argc
and the individual arguments as character arrays in the pointer array argv
.
So the command
myFilt p1 p2 p3
results in something like
Missing image
CCommandLineArgv.png
Image:CCommandLineArgv.png
(Note: there is no guarantee that the individual strings are contiguous.)
The individual values of the parameters may be accessed with argv[1]
, argv[2]
, and argv[3]
, as shown in the following program:
#include <stdio.h> int main(int argc, char *argv[]) { int i; printf ("argc\t= %i\n", argc); for (i = 0; i < argc; i++) printf ("argv[%i]\t= %s\n", i, argv[i]); return 0; }
Evaluation order
A conforming C compiler can evaluate expressions in any order between sequence points. Sequence points are defined by:
- Statement ends at semicolons.
- The sequencing operator: a comma.
- The short-circuit operators: logical and (
&&
) and logical or (||
). - The conditional operator (
?:
): This operator evaluates its first sub-expression first, and then its second or third (never both of them) based on the value of the first.
Expressions before a sequence point are always evaluated before those after a sequence point. In the case of short-circuit evaluation, the second expression may not be evaluated depending on the result of the first expression. For example, in the expression (a() || b())
, if the first argument evaluates to true
, the result of the entire expression will also be true
, so b()
is not evaluated.
Undefined behavior
An interesting (though certainly not unique) aspect of the C standards is that the behavior of certain code is said to be "undefined". In practice, this means that the program produced from this code can do anything, from working as intended, to crashing every time it is run.
For example, the following code produces undefined behavior, because the variable b
is operated on more than once in the expression a = b + b++;
:
#include <stdio.h> int main (void) { int a, b = 1; a = b + b++; printf ("%d\n", a); return 0; }
Because there is no sequence point between the access of b
in b + b++
, it is apparent the compiler could decide to increment b
before or after the addition, resulting in either 2 or 3. However, to allow the compiler to make certain optimizations the standard is even more pessimistic than this. In general, any separate modification and access of a value between sequence points invokes undefined behavior.
Bibliography
- Kernighan, Brian W. and Dennis M. Ritchie. The C Programming Language.
External links
- The syntax of C in Backus-Naur form (http://www.math.grin.edu/~stone/courses/languages/C-syntax.xhtml)
- Programming in C (http://www.cs.cf.ac.uk/Dave/C/CE.html)