C variable types and declarations
From Seo Wiki - Search Engine Optimization and Programming Languages
The C programming language has an extensive system for declaring variables of different types. The rules for the more complex types can be confusing at times, due to the decisions taken over their design. The principal decision is that the declaration of a variable should be similar, syntactically, to its use (declaration reflects use). This article presents a collection of variable declarations, starting at simple types, and proceeding to more complex types. No attempt is made to present code which actually uses the variables declared.
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.
char red; int blue;
Various qualifiers can be placed on these basic types, in order to further describe their type.
unsigned int x; signed int y; int z; /* Same as "signed int" */ unsigned char grey; signed char white;
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 non-negative values (positive or zero), while signed integers (default) can take both positive and negative values. 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.
int type can also be given a size qualifier, to specify more precisely the range of values (and memory size requirements) of the value stored.
short int yellow; long int orange; long long int red;
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.
long int brown; long brown;
Novice C programmers may be confused 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 be necessarily different. It is perfectly valid, for example, if all four types are 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
To help promote safety in programs, values can be marked as being constant with the
const type qualifier. Compilers must diagnose, usually with an error, attempts to modify such variables. Since
const variables 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 of the following declarations are therefore equivalent:
int const black = 12; const int black = 12;
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.
Variables can be declared as being pointers to values of various types, by means of the * type declarator. To declare a variable as a pointer, immediately precede its name with an asterisk.
char *square; long *circle;
K&R gives a good explanation for the slightly odd use of asterisks in this way, as well as a motivation for why they attach the asterisk onto the name of the variable, when it might seem to make more sense being attached to the name of the type. This is, that when you dereference the pointer, it has the type of the thing it points at. In this case,
*circle is 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:
long* first, second;
will result in
first being a pointer to a long, but
second being a long itself, which is likely not what a beginner expects.
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 (moreover, this casting may be implicit, meaning that the cast operator isn't needed when the new type is obvious, for example in variable assignments).
The pointed type can take all of the usual markings given above; the following are all valid pointer declarations.
long int *rectangle; unsigned short int *rhombus; const char *kite;
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.
char * const pentagon = &some_char;
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 const * const hexagon = &some_char; const char * const hexagon = &some_char;
Pointers to pointers
Because, for example,
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.
unsigned long const int * const *octagon;
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
struct pointers, or the use of the typedef keyword.
An array is a collection of values, all of the same type, stored contiguously in memory. In C, arrays are implemented as pointers, with some syntactic sugar to simplify memory allocation.
Some C-like languages, such as Java or C#, separate their array declarations into a type followed by a list of variable names. In these languages, an array of 10 integer values may be declared this way:
int cat; /* THIS IS NOT VALID C CODE */
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.
int cat; /* THIS IS VALID C CODE */
Arrays of arrays
Since they are pointers, arrays can be nested. Because the array notation, using square brackets (
), 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
Arrays of pointers
Because the element in an array is itself a C type, arrays of pointers can of course be constructed.
mice to be a 10 element array, where each element is a pointer to a
Pointers to arrays
As stated before, a bare array name (ie. without a subscription) is simply a pointer and can be passed around as such:
double dbls; ... myFunc(dbls); ...
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.
Arrays are pointers
The square-bracket array subscription notation, such as
my_array can be thought of as "take element number 5 from my_array", but it is also just as valid to think of this operation as "dereference the pointer which is equal to 'my_array + 5'". In fact, the following is perfectly valid code:
int *start_pointer = (int*) malloc(5*sizeof(int)); // Allocate memory, giving a pointer to the start start_pointer = 42; // Put the integer '42' in the fourth position of the allocated memory
This shows how a pointer can be used as an array, although the memory allocation must be given explicitly (and thus the memory must later be freed to prevent a memory leak). In a similar fashion, dereferencing a pointer using
*pointer_name can be done by asking for the "first element of the array" (ie. treating a pointer as a 1-element array) like this
pointer_name. Both are valid, as long as C's precedence rules are followed.
A function is an example of a derived type. The type of each parameter to a function is ordinarily specified, although strictly speaking it is not required for most functions. Specifying the name of each parameter is optional in a function declaration without a function body. The following declarations are equivalent:
long int bat(char); long int bat(char c);
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:
const int *ball(long int l, int i, unsigned char *s);
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 takes a function pointer as an argument, use the standard notation as given above:
int crowd(char p1, int (*p2)(void));
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:
int crowd(char p1, int p2(void));
To declare a function returning a function pointer (a so-called functional) again requires parentheses, to properly apply the function markings:
long int (*boundary(int height, int width))(int x, int y);
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:
typedef long int return_func(int x, int y); return_func *boundary(int height, int width);