Previous Table of Contents Next


Besides a type name, the operand of sizeof can also be a variable name (or, in fact, an arbitrary expression), in which case sizeof computes the size of that variable’s (or expression’s) type. For example, the preceding example could be rewritten as

ip = malloc(n * sizeof(*ip));

sizeof’s operand is now the expression *ip. This expression is not evaluated: no attempt is made to fetch or refer to the location pointed by ip; the compiler merely computes the size of the type of the expression, which is whatever type is pointed to by ip. With ip declared as shown here, therefore, the same answer is returned as for sizeof(int). Notice, however, that if ip’s type is ever changed so that it points to data of some other size, the sizeof expression involving *ip will automatically pick up the new type and size. (When sizeof is applied to an expression, as opposed to a type name, the parentheses around the operand are optional.)

When sizeof is applied to an array or other aggregate data structure (see section 3.7), the size computed is the total size of the object. For example, given our earlier array int a[10], the expression sizeof(a) would yield 10*sizeof(int). The operand of sizeof is therefore one context in which the “equivalence” rule—that an array reference automatically generates a pointer to the array’s first element—is not applied. (This makes sense because sizeof’s operand is not a place where the value of the array would be needed.) To compute the number of elements in an array, an expression of the form

sizeof(a) / sizeof(a[0])

can be used.

It is important to remember that sizeof does its work at compile time, based on the actual type of the expression to which it is applied. In each of the three fragments

f(int x[])
{
int n = sizeof(x);
}

and

int a[10], *b = a;
int n = sizeof(b);

and

int *ip = malloc(10 * sizeof(int));
int n = sizeof(ip);

the sizeof operator computes (and assigns to n) not the size of an array of 10 ints, nor even the size of one int, but rather the size of one pointer-to-int—that is, the same value that sizeof(int *) would return. (Similarly, expressions like sizeof(ip)/sizeof(ip[0]), where ip is a pointer, are meaningless.)

If the pointers returned by malloc can be used to point to any data type, what is the pointer type actually returned by malloc? The answer is a special, “generic” pointer type, void *, or “pointer to void.” The void type has no values, so it is meaningless to ask what a void pointer points to, but the void * type has the special property that the compiler automatically performs any appropriate conversions when a void * pointer value is assigned to some other type of data pointer or when some other type of data pointer is assigned to a void * pointer variable. That is, given that malloc is declared as returning void *, the initialization in

int *ip = malloc(sizeof int);

implies an automatic conversion, just as if we had written

int *ip = (int *)malloc(sizeof int);

See section 3.10.4.1 for more information about malloc and dynamic memory allocation, and about three more memory-management functions.

3.6.7. Pointers to Functions

Besides those pointers that point to data objects, it is also possible to declare pointers that point to functions. The syntax is slightly forbidding. The declaration

int (*funcptr)();

declares a pointer named funcptr, which will point to functions that return int. The syntax (*funcptr)() is an example of a complex declarator. The asterisk indicates that one level of indirection (one pointer level) is involved, and the second pair of parentheses indicates that a function is involved. The first pair of parentheses is there to get the precedence right, for there are precedence relationships in declarators just as there are in expressions. Without the parentheses, the declaration would be

int *funcptr();       /* WRONG, for pointer-to-function */

and would declare a function returning a pointer to int rather than a pointer to a function returning int. (We saw another example of a complex declaration in section 3.6.5, where the declarator (*x)[7] declared a function parameter x, which was a pointer to an array.)

Function pointers can also include prototypes for the parameter lists of the functions pointed to. The preceding example does not include a prototype; the second, empty pair of parentheses essentially means “taking unspecified arguments” (just as the empty parentheses in an old-style, pre-ANSI external function declaration do). If funcptr will always point to functions that accept, say, two parameters each of type float, that fact could be indicated by writing

int (*funcptr)(float, float);

(In fact, most of the time, we probably do know just what argument lists the functions pointed to by a given function pointer will accept because that list will show up in the actual calls to the pointed-to functions.)

Function pointer declarations can be simplified by the use of typedefs. For example, after declaring

typedef char *pc;        /* pointer to char */
typedef pc fpc();        /* function returning pointer to char */
typedef fpc *pfpc;       /* pointer to function returning… */

we arrive at a single type name, pfpc, embodying the type “pointer to function returning pointer to char,” which can then be used to declare pointer variables of this type:

pfpc fp1, fp2;

This declaration declares fp1 and fp2 just as if they had been declared in “longhand,” without the typedefs, as

char *(*fp1)(), *(*fp2)();


Previous Table of Contents Next