- C variable types and declarations
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.
Basic types
There are four basic types of variable in C; they are:
char
,int
,double
andfloat
.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.
ignedness
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
char
,signed char
,unsigned char
to be different types. Since most standardized string functions take pointers to plainchar
, many C compilers correctly complain if one of the other character types is used for strings passed to these functions.ize
The
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.When declaring a
short int
orlong int
, it is permissible to omit theint
, 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:
* A
short int
must not be larger than anint
.
* Anint
must not be larger than along int
.
* Ashort int
must be at least 16 bits long.
* Anint
must be at least 16 bits long.
* Along int
must be at least 32 bits long.
* Along long int
must 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 [http://archive.opengroup.org/public/tech/aspen/lp64_wp.htm 64-Bit Programming Models] . Two popular schemes are
ILP32
, in whichint
,long int
and pointer types are 32 bits long; andLP64
, in whichlong int
and pointers are 64 bits, andint
are 32 bits. Most implementations under these schemes use 16-bitshort int
s.A
double
variable can be marked as being along double
, which the compiler may use to select a larger floating point representation than a plaindouble
. Again, the standard is unspecific on the relative sizes of the floating point values, and only requires afloat
not to be larger than adouble
, which should not be larger than along double
.Type qualifiers
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. Sinceconst
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 asint
. Both the following declarations are therefore equivalent:While the former more closely reflects the use ofconst
marking when used in pointer types, the latter form is more natural and almost ubiquitous.Pointer types
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.
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 youdereference the pointer, it has the type of the thing it points at. In this case,*circle
is a value of typelong
. 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 aslong* 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 aconst char
. The value ofkite
itself is not a constant, only the value of thechar
to which it points. The placement ofconst
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.Here,
pentagon
is a constant pointer, which points at achar
. 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.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.This declares
octagon
as a pointer to a constant pointer to a constantunsigned 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 ofstruct
pointers.See also [http://c2.com/cgi/wiki?ThreeStarProgrammer Three Star Programmer] at the
WikiWikiWeb .Arrays
Some C-like languages, such as Java or C#, separate their 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:
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.Arrays of arrays
As with pointers, array types 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 12double
values.Arrays of pointers
Because the element type of an array is itself a C type, arrays of pointers can of course be constructed.
This declares
mice
to be a 10 element array, where each element is a pointer to achar
.Pointers to arrays
The array name on its own evaluates to a pointer and can be passed around as such for many applications:
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 20double
values.To declare a pointer to an array of pointers, simply combine the notations.
Functions
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: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 adouble
, but says nothing about the parameters the function takes. (InC++ , however, this declaration means thatumpire
takes no parameters, the same as the declaration that usesvoid
.)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 plainchar
. Its second argument,p2
is a pointer to a function. This pointed-to function should be given no arguments, and will return anint
.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,height
andwidth
, and returns a function pointer. The returned pointer points at a function that itself takes two integer parameters,x
andy
, and returns along
.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:ee also
*
C syntax
*uninitialized variable
Wikimedia Foundation. 2010.