codepoet
codepoet

Reputation: 222

Clarification of NULL assignment to char *

I came across an example of non-portable C code where a char pointer is an argument to a variadic C function. The example is described in the image below. The part highlighted in blue is not necessarily clear, and appears wrong. In particular, I have two questions:

  1. Assuming that NULL was 32-bit int 0 on a system, wouldn't compiler do an implicit cast of 32-bit - int to 64-bit 0 when it encounters char *string = NULL. If not, then are we saying that each expression like char *string = NULL is non-portable and must be always replaced with an explicit cast like char *string = (char *)NULL for portable C?

  2. If NULL was 32-bit int 0, and char *string was 64-bit then why would printf run out of bits to print like it is suggested in the blue highlight. Shouldn't printf get full 64 bits as it was passed string and not NULL.

Noncompliant Code Example (NULL)

The C Standard allows NULL to be either an integer constant or a pointer constant. While passing NULL as an argument to a function with a fixed number of arguments will cause NULL to be cast to the appropriate pointer type, when it is passed as a variadic argument, this will not happen if sizeof(NULL) != sizeof(void *). This is possible for several reasons:

  • Pointers and ints may have different sizes on a platform where NULL is an integer constant
  • The platform may have different pointer types with different sizes on a platform. In that case, if NULL is a void pointer, it is the same size as a pointer to char (C11 section 6.2.5, paragraph 28), which might be sized differently than the required pointer type.

On either such platform, the following code will have [undefined behavior][1]:

char* string = NULL;
printf("%s %d\n", string, 1);

On a system with 32-bit int and 64-bit pointers, printf() may interpret the NULL as high-order bits of the pointer and the third argument 1 as the low-order bits of the pointer. In this case, printf() will print a pointer with the value 0x00000001 and then attempt to read an additional argument for the %d conversion specifier, which was not provided.

Compliant Solution (NULL)

This compliant solution avoids sending NULL to printf():

char* string = NULL;
printf("%s %d\n", (string ? string : "null"), 1);

(Source)

Upvotes: 0

Views: 695

Answers (1)

Eric Postpischil
Eric Postpischil

Reputation: 222763

The referenced article is wrong and should be disregarded.

  1. Assuming that NULL was 32-bit int 0 on a system, wouldn't compiler do an implicit cast of 32-bit - int to 64-bit 0 when it encounters char *string = NULL.

An assignment automatically converts the right operand to the type of the left operand. So char *string = NULL will convert the NULL value to char *, not to “64-bit 0”.

If not, then are we saying that each expression like char *string = NULL is non-portable and must be always replaced with an explicit cast like char *string = (char *)NULL for portable C?

No, char *string = NULL is portable C code; it is strictly conforming.

  1. If NULL was 32-bit int 0, and char *string was 64-bit then why would printf run out of bits to print like it is suggested in the blue highlight. Shouldn't printf get full 64 bits as it was passed string and not NULL.

The code referenced, char* string = NULL; followed by printf("%s %d\n", string, 1);, does not pass NULL to printf. It passes string to printf, and the prior assignment converts NULL to char *. So printf is passed a char * that has the value of a null pointer. This will not cause any problem in interpreting the variable arguments to printf. (It is, however, improper to pass a null pointer for the %s conversion.)

If the call were instead printf("%s", NULL);, then there is a problem. Arguments corresponding to the ... part of a variable-argument function are not automatically converted to a parameter type. They are processed by the default argument promotions, which largely promote narrow integer types to int and promote float to double, but they will not convert an int to any type of pointer. Thus, if NULL is defined as 0, then printf("%s", NULL); passes an int where a char * is expected, and this may cause various misinterpretations of the arguments.

In consequence, never use the NULL macro as a direct argument to a function with a variable argument list. Using a pointer variable that has been assigned from NULL is okay.

Upvotes: 2

Related Questions