There are four basic types of variable in C; they are:
|The most basic unit addressable by the machine; typically a single octet(one byte). This is an integral type.|
|The most natural size of integer for the machine; typically a whole 16, 32 or 64-bit(2, 4, or 8 bytes respectively) addressable word.|
|A single-precision floating point value.|
|A double-precision floating point value.|
To declare a variable of any of these basic types, the name of the type is given first, then the name of the new variable second.
Various qualifiers can be placed on these basic types, in order to further describe their type.
If signed, the most significant bit designates a positive or negative value leaving the remaining bits to be used to hold a designated value. Unsigned integers can only take positive numbers, while signed integers (default) can take both positive and negative numbers. The advantage of unsigned integers is to allow a greater range of positive values (e.g. 0 → +65535 depending on the size of the integer), whereas signed integers allow only up to half the same number as positive integers and the other half as negative integers (e.g. −32768 → +32767).
An unsigned character, depending on the code page, might access an extended range of characters from 0 → +255, instead of that accessible by a signed char from −128 → +127, or it might simply be used as a small integer. The standard requires
unsigned char to be different types. Since most standardized string functions take pointers to plain
char, many C compilers correctly complain if one of the other character types is used for strings passed to these functions.
inttype can also be given a size qualifier, to specify more precisely the range of values (and memory size requirements) of the value stored.
When declaring a
short int or
long int, it is permissible to omit the
int, as this is implied. The following two declarations are equivalent.
There is some confusion in novice C programmers as to how big these types are. The standard is specifically vague in this area:
short intmust not be larger than an
intmust not be larger than a
short intmust be at least 16 bits long.
intmust be at least 16 bits long.
long intmust be at least 32 bits long.
long long intmust be at least 64 bits long.
The standard does not require that any of these sizes are necessarily different. It is perfectly valid, for example, that all four types be 64 bits long. In order to allow a simple and concise description of the sizes a compiler will apply to each of the four types (and the size of a pointer type; see below), a simple naming scheme has been devised; see 64-Bit Programming Models Two popular schemes are
ILP32, in which
long int and pointer types are 32 bits long; and
LP64, in which
long int and pointers are 64 bits, and
int are 32 bits. Most implementations under these schemes use 16-bit
double variable can be marked as being a
long double, which the compiler may use to select a larger floating point representation than a plain
double. Again, the standard is unspecific on the relative sizes of the floating point values, and only requires a
float not to be larger than a
double, which should not be larger than a
consttype qualifier. Compilers must diagnose, usually with an error, attempts to modify such variables. Since
constvariables cannot be assigned to, they must be initialized at the point of declaration.
The C standard permits arbitrary ordering of type qualifiers, such as
const, and type specifiers, such as
int. Both the following declarations are therefore equivalent:
While the former more closely reflects the use of
const marking when used in pointer types, the latter form is more natural and almost ubiquitous.
*circleis a value of type
long. While this may be a subtle point to raise in this case, it starts to show its worth when more complex types are used. This is the reason for C's slightly odd way of declaring more complex types, when the name of the actual variable gets hidden within the type declaration, as further examples will show. However, the standard also allows you to attach the asterisk to the name of the type such as
long* circle. This form is usually discouraged since it can confuse the novice when multiple pointers are declared on the same line.
There is a special type of value which cannot be directly used as a variable type, but only as pointed type in the case of pointer declarations.
The pointed value here cannot be used directly; attempts to dereference this pointer will result in a compiler error. The utility here is that this is a generic pointer; useful when the pointed type does not matter, simply the pointer address is needed. It is usually used to store pointers in utility types, such as linked lists, or hash tables, where the code using the utility will typecast it to a pointer of some specific type.
The pointed type can take all of the usual markings given above; the following are all valid pointer declarations.
Note specifically the use of
const in this last case. Here,
kite is a (non-
const) pointer to a
const char. The value of
kite itself is not a constant, only the value of the
char to which it points. The placement of
const before the type, as noted above, gives motivation for the way a constant pointer is declared. As it is constant, it must be initialised when it is declared.
pentagon is a constant pointer, which points at a
char. The value at which it points is not a constant; it will not be an error to modify the pointed character; only to modify the pointer itself. It is also possible to declare both the pointer and the pointed value as being constant. The following two declarations are equivalent.
char *is itself a type, pointer variables can be declared which point at values of such a type. These are pointers to pointers.
As before, the usual type qualifiers and
const marking can be applied.
octagon as a pointer to a constant pointer to a constant
unsigned long integer. Pointer types can be arbitrarily nested below this, but their use is increasingly harder to think clearly about, the more levels of indirectness are involved. Any code using more than two levels of pointer probably requires a redesign, in terms of
However, as noted above, C's declaration syntax aims to make declarations resemble use. Because an access into this array would look like
cat[i], it is declared in a different syntax in C.
), is a postfix notation, the size of the inner nested array types is given after the outer type.
This declares that
dog is an array containing 5 elements. Each element is an array of 12
mice to be a 10 element array, where each element is a pointer to a
To declare a variable as being a pointer to an array, we must make use of parentheses. This is because in C brackets () have higher precedence than the asterisk (*). So if we wish to declare a pointer to an array, we need to supply parentheses to override this:
This declares that
elephant is a pointer, and the type it points at is an array of 20
To declare a pointer to an array of pointers, simply combine the notations.
While both forms are syntactically correct, it is usually considered bad form to omit the names of the parameters when writing function declarations in header files. These names can provide valuable clues to readers of such files, as to their meaning and operation.
Functions can take and return pointer types using the usual notation for a pointer:
The special type
void is useful for declaring functions which do not take any parameters at all:
This is quite different from the empty set of parentheses used in C to declare a function without information on its parameter types:
This declaration declares a function called
umpire which returns a
double, but says nothing about the parameters the function takes. (In C++, however, this declaration means that
umpire takes no parameters, the same as the declaration that uses
In C, functions cannot directly take other functions as parameters, or return functions as results. However, they can take or return pointers to them. To declare that a function take a function pointer as an argument, use the standard notation as given above:
Here, we have a function which takes two arguments. Its first argument,
p1, is a plain
char. Its second argument,
p2 is a pointer to a function. This pointed-to function should be given no arguments, and will return an
As a special case, C implementations treat function parameters declared with a function type as pointer-to-function types. Thus, the following declaration and the preceding declaration are equivalent:
To declare a function returning a function pointer (a so-called functional) again requires parentheses, to properly apply the function markings:
As there are two sets of argument lists, this declaration should be read carefully, as it is quite subtle. Here, we are defining a function called
boundary. This function takes two integer parameters,
width, and returns a function pointer. The returned pointer points at a function that itself takes two integer parameters,
y, and returns a
This type of marking can be arbitrarily extended, to make functions that return pointers to functions that return pointers to functions, and so on, but it quickly gets very unreadable, and prone to bugs. Use of a
typedef improves readability, as shown by the following declarations that are equivalent to the previous declaration: