4.2. Conversion of Nonarithmetic Types
Pointers and the
4.2.1. Array and Function Designators
An array or function designator is any expression that has an array or function type. In most cases, the compiler implicitly converts an expression with an array type, such as the
The following examples
#include <stdio.h>
int *iPtr = 0; // A pointer to int, initialized with 0.
int iArray[ ] = { 0, 10, 20 }; // An array of int, initialized.
int array_length = sizeof(iArray) / sizeof(int); // The number of elements:
// in this case, 3.
printf("The array starts at the address %p.\n", iArray);
*iArray = 5; // Equivalent to iArray[0] = 5;
iPtr = iArray + array_length - 1; // Point to the last element of iArray:
// Equivalent to
// iPtr = &iArray[array_length-1];
printf("The last element of the array is %d.\n", *iPtr);
In the initialization of
array_length
in this example, the expression
sizeof(iArray)
yields the size of the whole array, not the
The names of character arrays are used as pointers in string operations, as in this example:
#include <stdio.h>
#include <string.h> // Declares size_t strlen( const char *s )
char msg[80] = "I'm a string literal."; // Initialize an array of char.
printf("The string is %d characters long.\n", strlen(msg));
// Answer: 21.
printf("The array named msg is %d bytes long.\n", sizeof(msg));
// Answer: 80.
In the function call strlen(msg) in this example, the array identifier msg is implicitly converted to a pointer to the array's first element with the function parameter's type, const char * . Internally, strlen( ) merely counts the characters beginning at that address until the first null character, the string terminator. Similarly, any expression that designates a function, such as a function name, can also be implicitly converted into a pointer to the function. Again, this conversion does not apply when the expression is the operand of the address operator & . The sizeof operator cannot be used with an operand of function type. The following example illustrates the implicit conversion of function names to pointers. The program initializes an array of pointers to functions, then calls the functions in a loop.
#include <stdio.h>
void func0( ) { puts("This is the function func0( ). "); } // Two functions.
void func1( ) { puts("This is the function func1( ). "); }
/* ... */
void (*funcTable[2])(void) = { func0, func1 }; // Array of two pointers to
// functions returning void.
for ( int i = 0; i < 2; ++i ) // Use the loop counter as the array index.
funcTable[i]( );
4.2.2. Explicit Pointer ConversionsTo convert a pointer from one pointer type to another, you must usually use an explicit cast. In some cases the compiler provides an implicit conversion: these cases are described in "Implicit Pointer Conversions," later in this chapter. Pointers can also be explicitly converted into integers, and vice versa. 4.2.2.1. Object pointersYou can explicitly convert an object pointerthat is, a pointer to a complete or incomplete object typeto any other object pointer type. In your program, you must ensure that your use of the converted pointer makes sense. An example:
float f_var = 1.5F;
long *l_ptr = (long *)&f_var; // Initialize a pointer to long with
// the address of f_var.
double *d_ptr = (double *)l_ptr; // Initialize a pointer to double with
// the same address.
// On a system where sizeof(float) equals sizeof(long):
printf( "The %d bytes that represent %f, in hexadecimal: 0x%lX\n",
sizeof(f_var), f_var, *l_ptr );
// Using a converted pointer in an assignment can cause trouble:
/* *d_ptr = 2.5; */ // Don't try this! f_var's location doesn't
// have space for a double value!
*(float *)d_ptr = 2.5; // OK: stores a float value in that location.
If the object pointer after conversion does not have the alignment required by the new type, the results of using the pointer are undefined. In all other cases, converting the pointer value back into the original pointer type is
If you convert any type of object pointer into a pointer to any
char
type (
char
,
signed char
, or
unsigned char
), the result is a pointer to the first byte of the object. The first byte is
#include <stdio.h>
struct Data {
short id;
double val;
};
struct Data myData = { 0x123, 77.7 }; // Initialize a structure.
unsigned char *cp = (unsigned char *)&myData; // Pointer to the first
// byte of the structure.
printf( "%p: ", cp ); // Print the starting
// address.
for ( int i = 0; i < sizeof(myData); ++i ) // Print each byte of the
printf( "%02X ", *(cp + i) ); // structure, in hexadecimal.
putchar( '\n' );
This example produces output like the following: 0xbffffd70: 23 01 00 00 00 00 00 00 CD CC CC CC CC 6C 53 40 The output of the first two bytes, 23 01 , shows that the code was executed on a little-endian system: the byte with the lowest address in the structure myData was the least significant byte of the short member id . 4.2.2.2. Function pointersThe type of a function always includes its return type, and may also include its parameter types. You can explicitly convert a pointer to a given function into a pointer to a function of a different type. In the following example, the typedef statement defines a name for the type "function that has one double parameter and returns a double value":
#include <math.h> // Declares sqrt( ) and pow( ).
typedef double (func_t)(double); // Define a type named func_t.
func_t *pFunc = sqrt; // A pointer to func_t, initialized with
// the address of sqrt( ).
double y = pFunc( 2.0 ); // A correct function call by pointer.
printf( "The square root of 2 is %f.\n", y );
pFunc = (func_t *)pow; // Change the pointer's value to the
// address of pow( ).
/* y = pFunc( 2.0 ); */ // Don't try this: pow( ) takes two
// arguments.
In this example, the function pointer pFunc is assigned the addresses of functions that have different types. However, if the program uses the pointer to call a function whose definition does not match the exact function pointer type, the program's behavior is undefined. 4.2.3. Implicit Pointer ConversionsThe compiler converts certain types of pointers implicitly. Assignments, conditional expressions using the equality operators == and != , and function calls involve implicit pointer conversion in three kinds of cases, which are described individually in the sections that follow. The three kinds of implicit pointer conversion are:
4.2.3.1. Pointers to voidPointers to void that is, pointers of the type void * are used as "multipurpose" pointers to represent the address of any object, without regard for its type. For example, the malloc( ) function returns a pointer to void (see Example 2-3). Before you can access the memory block, the void pointer must always be converted into a pointer to an object.
Example 4-1
void qsort( void *
array
, size_t
n
, size_t
element_size
,
int (*
compare
)(const void *, const void *) );
The
qsort( )
function sorts the array in
The fourth parameter,
compare
, is a pointer to a function that
qsort( )
calls to compare any two array elements. The addresses of the two elements to be compared are passed to this function in its pointer parameters. Usually this comparison function must be defined by the programmer. It must return a value that is less than, equal to, or greater than 0 to
Example 4-1. A comparison function for qsort( )
#include <stdlib.h>
#define ARR_LEN 20
/*
* A function to compare any two float elements,
* for use as a call-back function by qsort( ).
* Arguments are passed by pointer.
*
* Returns: -1 if the first is less than the second;
* 0 if the elements are equal;
* 1 if the first is greater than the second.
*/
int floatcmp( const void* p1, const void* p2 )
{
float x = *(float *)p1,
y = *(float *)p2;
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
/*
* The main( ) function sorts an array of float.
*/
int main( )
{
/* Allocate space for the array dynamically: */
float *pNumbers = malloc( ARR_LEN * sizeof(float) );
/* ... Handle errors, initialize array elements ... */
/* Sort the array: */
qsort( pNumbers, ARR_LEN, sizeof(float), floatcmp );
/* ... Work with the sorted array ... */
return 0;
}
In Example 4-1, the
malloc( )
function returns a
void *
, which is implicitly converted to
float *
in the assignment to
pNumbers
. In the call to
qsort( )
, the first argument
pNumbers
is implicitly converted from
float *
to
void *
, and the function name
floatcmp
is implicitly interpreted as a function pointer. Finally, when the
floatcmp( )
function is called by
qsort( )
, it receives arguments of the type
void *
, the "universal" pointer type, and must convert them explicitly to
float *
before dereferencing them to initialize its
float
4.2.3.2. Pointers to qualified object typesThe type qualifiers in C are const , volatile , and restrict (see Chapter 11 for details on these qualifiers). For example, the compiler implicitly converts any pointer to int into a pointer to const int where necessary. If you want to remove a qualification rather than adding one, however, you must use an explicit type conversion, as the following example illustrates:
int n = 77;
const int *ciPtr = 0; // A pointer to const int.
// The pointer itself is not constant!
ciPtr = &n; // Implicitly converts the address to the type
// const int *.
n = *ciPtr + 3; // OK: this has the same effect as n = n + 3;
*ciPtr *= 2; // Error: you can't change an object referenced by
// a pointer to const int.
*(int *)ciPtr *= 2; // OK: Explicitly converts the pointer into a
// pointer to a nonconstant int.
The second to last statement in this example illustrates why pointers to const -qualified types are sometimes called read-only pointers : although you can modify the pointers' values, you can't use them to modify objects they point to. 4.2.3.3. Null pointer constantsA null pointer constant is an integer constant with the value 0, or a constant integer value of 0 cast as a pointer to void . The macro NULL is defined in the header files stdlib.h , stdio.h , and others as a null pointer constant. The following example illustrates the use of the macro NULL as a pointer constant to initialize pointers rather than an integer zero or a null character:
#include <stdlib.h>
long *lPtr = NULL; // Initialize to NULL: pointer is not ready for use.
/* ... operations here may assign lPtr an object address ... */
if ( lPtr != NULL )
{
/* ... use lPtr only if it has been changed from NULL ... */
}
When you convert a null pointer constant to another pointer type, the result is called a
null pointer
. The bit pattern of a null pointer is not
4.2.4. Conversions Between Pointer and Integer TypesYou can explicitly convert a pointer to an integer type, and vice versa. The result of such conversions depends on the compiler, and should be consistent with the addressing structure of the system on which the compiled executable runs. Conversions between pointer and integer types can be useful in system programming, and necessary when programs need to access specific physical addresses, such as ROM or memory-mapped I/O registers. When you convert a pointer to an integer type whose range is not large enough to represent the pointer's value, the result is undefined. Conversely, converting an integer into a pointer type does not necessarily yield a valid pointer. A few examples:
float x = 1.5F, *fPtr = &x; // A float, and a pointer to it.
unsigned int adr_val = (unsigned int)fPtr; // Save the pointer value
// as an integer.
/*
* On an Intel x86 PC in DOS, the BIOS data block begins at the
* address 0x0040:0000.
* (Compile using DOS's "large" memory model.)
*/
unsigned short *biosPtr = (unsigned short *)= 0x400000L;
unsigned short com1_io = *biosPtr; // The first word contains the
// I/O address of COM1.
printf( "COM1 has the I/O base address %Xh.\n", com1_io );
The last three statements obtain information about the hardware configuration from the system data table,
|