Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does pointer != NULL, but prints as (nil) with %p format specifier?

Tags:

c

pointers

gcc

I'm experiencing an unusual issue in my C program where a pointer that is checked to be not NULL prints as (nil) when using the %p format specifier.

Here's a simplified version of my code:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char *argv[]){

    int arg1 = atoi(argv[1]);
    int arg2 = atoi(argv[2]);

    double * res = NULL; 
    res = (double *) malloc(sizeof(double)* 100);
    
    double * tmp = NULL; 

    for (; arg1 != arg2; ++arg1)
    {
        tmp = (double *)realloc(tmp, sizeof(double) * 1);
    }
    
    memcpy(res, tmp, 0);
    
    if(tmp != NULL){
        printf("addr =  %p\n", tmp);
        printf("val =  %u\n", tmp);
    }

    return 0;
}

I compiled and run with :

gcc ./test.c -O2
./a.out 1 1

Despite checking that tmp is not NULL, the output I get is:

addr =  (nil)
val =  0

My understanding is that a pointer with the address 0 represents NULL.

But I compiled and run with :

gcc ./test.c
./a.out 1 1

There no output.

I've compiled this code using GCC 11.4.0 on Ubuntu 22.04.4 LTS.

What could be causing this discrepancy? How does flags O2 effect the output? Any insights would be greatly appreciated.

like image 899
Blueha Avatar asked Nov 01 '25 02:11

Blueha


2 Answers

What likely happened in your case is:

With no optimization, the compiler implemented the code directly. tmp was initialized to NULL, the for loop executed no iterations because arg1 equalled arg2, and the “then” clause of the if statement was not executed because tmp != NULL evaluated as false.

With optimization, the fact that tmp is passed to memcpy, even with a length of zero, induces an assertion that tmp is not null. The optimizer uses this assertion to eliminate evaluation of tmp != NULL, so the “then” clause of the if is always executed, unconditionally. Thus printf is called with a null pointer passed to it.

Other compilers may behave differently, including GCC with different switches.

like image 61
Eric Postpischil Avatar answered Nov 03 '25 14:11

Eric Postpischil


Running ./a.out 1 1 as you did means that both arg1 and arg2 will be 1.
This means that the for loop will not be executed at all (the condition arg1 != arg2 will never be true).
This in turn mean that tmp will stay initialized to NULL.

But in the following line:

memcpy(res, tmp, 0);

tmp is the argument for the src parameter of memcpy.

As you can see in the memcpy documentation:

The behavior is undefined if either dest or src is an invalid or null pointer.

Therefore the memcpy line above invokes UB (undefined behavior):

Once UB ensues there is no guarantee for the behavior of the program, so any output is in principle possible.

like image 20
wohlstad Avatar answered Nov 03 '25 16:11

wohlstad