Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the special status of the value 0 in c++?

This is purely a philosophical question. I assume that there's no reasonable context in which the result will prove to be useful (given nullptr).

According to this - https://en.cppreference.com/w/cpp/language/integer_literal, the type of integral literals is either int, long int, long long int, unsigned int, unsigned long int or unsigned long long int, with possible implementation-specific exceptions if the value of the literal doesn't fit into any of the above. None of these types are convertible to void *, unless the value of the literal is 0.

Different compilers handle this differently. For example, consider the following conversions:

void g(void * p){}

void f(){
    int i = 0;
    void * p;
    // p = i; // Fails. Also for other integral types.

    p = 0; // Works. Also for 00, 0x0 and 0b0. Also when adding `u` and `l` suffixes.
    g(0); // Also works.    
    // g(1); // Fails.  

    // Amazingly, even this seems to work with gcc, icc and msvc, but not with clang:
    void * x = static_cast<int>(0);
    // These works for icc and msvc, but fails with gcc and clang
    p = static_cast<int>(0);
    g(static_cast<int>(0));
}

What happens "under the hood" that enables compilers to perform these int->void * conversions?


Edit: Specifically, the question is what does the standard has to say about this?

like image 371
Benny K Avatar asked Sep 16 '25 05:09

Benny K


1 Answers

The question is, why is this permitted according to the standard

Because there needs to be a way to express null pointer. The designer of the C language chose that 0 would be null. The designer of C++ chose to be compatible with C, and as such 0 is a null pointer constant.

Later in C++11, nullptr was introduced as a new keyword. The integral null pointer constants cannot be replaced because that would break backward compatibility, so these different ways to express null co-exist. There is no reason to use 0 as null pointer if you don't need to support pre-C++11 systems.

and specifically what is permitted

Standard says (latest draft):

[conv.ptr] A null pointer constant is an integer literal ([lex.icon]) with value zero or a prvalue of type std​::​nullptr_­t. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type ([basic.compound]) and is distinguishable from every other value of object pointer or function pointer type. Such a conversion is called a null pointer conversion. Two null pointer values of the same type shall compare equal. The conversion of a null pointer constant to a pointer to cv-qualified type is a single conversion, and not the sequence of a pointer conversion followed by a qualification conversion ([conv.qual]). A null pointer constant of integral type can be converted to a prvalue of type std​::​nullptr_­t. [ Note: The resulting prvalue is not a null pointer value. — end note ]


What happens "under the hood" that enables compilers to perform these int->void * conversions?

The compiler parses the source. The grammar says that 0 is a literal. The compiler treats is as a literal 0, and as such lets it be converted to any pointer type as per standard.


// Amazingly, even this seems to work with gcc, icc and msvc, but not with clang:
void * x = static_cast<int>(0);

This is ill-formed since C++11. When an ill-formed program compiles, it is typically either because

  1. It is a language extension or
  2. It is a compiler bug or
  3. It is well formed in older version of language, and compiler targets that

In this case, it is probably a language extension.

// These works for icc and msvc, but fails with gcc and clang
p = static_cast<int>(0);
g(static_cast<int>(0));

These are also ill-formed since C++11. I don't know enough about icc and msvc to tell you whether these cases are intentional. I recommend checking their documentation regarding that.

like image 147
eerorika Avatar answered Sep 17 '25 19:09

eerorika