NULL Define in c & c++ differs?!

btaylor2401
Pointers in C do not *act* like integers. Integer math does not apply directly to pointer computation, confusing integer and pointer types is a really good way to create all sort of nasty, nasty bugs:
1
2
3
4
5
6
7
8
9
int i = 0;
char * p1 = 0;
uint16_t * p2 = 0;
uint32_t * p4 = 0;

++i;  //  i = 1
++p1; // p1 = 1
++p2; // p2 = 2
++p4; // p4 = 4

This difference in operation in itself prevents bugs (because you can actually use pointer math effectively without including sizeof() everywhere.) And the fact that pointers hold integer values *is* useful information, and impacts your use of them. But the types should not be conflated, because the operations are different.

C is not assembly. It is designed to do some semantic abstraction of the CPUs operation.


I agree that confusing types is a bad thing, but I do think that understanding a pointer is just a number in a register is not a bad thing. Just because C is an abstraction does not justify all other abstractions. Your example doesn't really apply to the case of void pointers, since we cannot do pointer arithmetic with void pointers. The discussion is about the concept of null pointers and the value of 0, not necessarily the type of the integer constant 0.

I don't see how 0, nullptr, NULL or ((void*)0) are each going to prevent different pointer *runtime* bugs. There is no pointer arithmetic involved when assigning a pointer to null. There is no pointer arithmetic involved in checking to see if a pointer is null. These operations only check the bits of a register with the bits in another register, i.e. if all the bits are 0 then the pointer is considered null. I don't see how confusion can arise from this and cause an actual bug.

To me it sounds like the gripe is "If we say null pointers are 0 programmers will think they are integers and then do incorrect pointer arithmetic". I think this is a poor justification for adding in a fancy nullptr abstraction. If anything new programmers who naively think adding +1 to a pointer moves forward by a byte will quickly learn fundamental ideas about C... I don't see this as a bad thing.

Actually now that I take a second look at your example, this looks like a *good* way to learn about pointer arithmetic. A new programmer can easily see the difference between 0 and 1, or 0 and 4. Personally this is how I originally learned about pointer arithmetic: assign pointers to 0 and add 1 to them.

Edited by Randy Gaul on
ratchetfreak
I just have an issue with the fact that "void* ptr = 0;" was ever valid code.

But why? Do you also have an issue with saying "long long value = 0;" instead of, say, "long long value = 0LL;"? Or how about "float value = 0;" instead of "float value = 0.0f;"?

- Casey
cmuratori

But why? Do you also have an issue with saying "long long value = 0;" instead of, say, "long long value = 0LL;"? Or how about "float value = 0;" instead of "float value = 0.0f;"?

- Casey


An numeric value is not a pointer if you want to convert a numeric value into a pointer you should be explicitly casting in all cases. I don't have issues with implicit arithmetic type promotion.

From doing a bit of research it seems that C had the right idea. There the NULL macro just had to be a null pointer that will implicitly convert to any pointer type how they did that was left to the implementation. This can be (and typically is) implemented by a void* null pointer because void* will implicitly convert to any other pointer type.

In C++ this is not longer possible because void* cannot implicitly convert to other pointer types anymore. So the C++ committee apparently decided to dictate how to implement the NULL macro by requiring that the NULL macro must be defined as 0. However if they had left it alone then the compiler writers could have created their own nullptr equivalent instead of being forced to complicate their already non-trivial type-promotion code to allow 0 constants to be promoted to pointers.

[source http://en.cppreference.com/w/cpp/types/NULL and http://en.cppreference.com/w/c/types/NULL plus a bit of logical reasoning]
There is a reason nullptr was introduced into c++ that wasn't mentioned yet. It is a consequence of how c++ was designed and before its introduction one feature of c++ was kinda broken.

Just using 0 or NULL for null pointers isn't problematic when handling pointers, if all you do is if( p == 0 ), the problem is this:
1
2
void foo( int x );
void foo( void* x );

Now if you call foo( NULL ), you would maybe expect the second version to be called, which is wrong.
You might say that people should have never started to use NULL and writing foo( 0 ) makes it clear that the first version will be called, but that still creates the problem that you can't call the second version with a null pointer without an explicit cast.
Which means that if you want the feature argument dependant lookup to work correctly, you would need to be explicit with your null pointers. So if you are explicitly casting everytime when you use a null pointer, you can just as well use nullptr, which does do the right thing, since it can't be implicitly casted to an int. Which means, that if you want to be consistent in your code, that you always need to use nullptr whenever you are handling pointers.

Good idea, bad idea? Who knows, but c++ needed nullptr to be "complete" as a consequence of its design.
Same thing with template specialization.
I really just have a hard time seeing these sorts of things as anything but silly. As an external observer, the idea that "you have to always cast 0 to a void * in order to specifically call it a pointer" is somehow this big motivating factor in why you would want nullptr is really strange. Are we just talking about the fact that (void*)0 is one more character to type than nullptr? I mean do people really think that was a good reason to add yet another concept to an already bloated language?

I would argue that (void *)0 is _much_ better than nullptr, because _at least it's obvious what it is_. Case in point, something like this thread is never going to happen if you just always have (void *)0, because it's obvious that it's just the value 0 being specifically made into a pointer type. Contrast that with NULL or nullptr, which _nobody_ knows what they mean unless you've gone and read some additional documentation that discusses it. They're totally ancillary and additional concepts, which happen to also be unnecessary.

Languages should add features that _actually do something useful_. Things like NULL and nullptr are the kinds of things that drive me nuts, because they don't actually do anything, and they just end up obscuring what the program is doing for no apparent benefit, and adding more things for beginners to learn that don't end up allowing them to write more powerful programs.

It's busywork - for the language designers, the language implementors, and the programmers who use the language :(

- Casey
I'm not against (void*)0 as a null pointer but I'm against 0 as a null pointer.

However in C++ you cannot use (void*)0 as a null pointer because the language does not convert void* to any other type of pointer, "int* i = (void*)0;" is a compile error.
ratchetfreak
An numeric value is not a pointer if you want to convert a numeric value into a pointer you should be explicitly casting in all cases.



But why? You even went as far as to say this was a "C design flaw". Multiple posts here have shown practical value in assigning pointers to 0 explicitly, and Casey mentioned this exact topic (ptr = 0) in his stream before where he gives his own justifications.

So surely, you have some opposing justification showing actual value in preventing a pointer from ever being assigned to the integer literal constant 0. As you've stated, this should be prevented with a special identifier, so that identifier must have justifications too. Otherwise you wouldn't come into an educational forum and make such claims, right?

Edited by Randy Gaul on
cmuratori
I really just have a hard time seeing these sorts of things as anything but silly. As an external observer, the idea that "you have to always cast 0 to a void * in order to specifically call it a pointer" is somehow this big motivating factor in why you would want nullptr is really strange. Are we just talking about the fact that (void*)0 is one more character to type than nullptr? I mean do people really think that was a good reason to add yet another concept to an already bloated language?

I would argue that (void *)0 is _much_ better than nullptr, because _at least it's obvious what it is_. Case in point, something like this thread is never going to happen if you just always have (void *)0, because it's obvious that it's just the value 0 being specifically made into a pointer type. Contrast that with NULL or nullptr, which _nobody_ knows what they mean unless you've gone and read some additional documentation that discusses it. They're totally ancillary and additional concepts, which happen to also be unnecessary.

Languages should add features that _actually do something useful_. Things like NULL and nullptr are the kinds of things that drive me nuts, because they don't actually do anything, and they just end up obscuring what the program is doing for no apparent benefit, and adding more things for beginners to learn that don't end up allowing them to write more powerful programs.

It's busywork - for the language designers, the language implementors, and the programmers who use the language :(

- Casey


Well said, Casey! :)
But why? Do you also have an issue with saying "long long value = 0;" instead of, say, "long long value = 0LL;"? Or how about "float value = 0;" instead of "float value = 0.0f;"?

- Casey
Well, if you have a function that is overloaded to take either a int or a pointer, the strong typing of nullptr is nice, same applies to long long and double/float. Using nullptr also makes it clear that I am talking about a pointer value (that happens to be 0), not just some integer. Allowing for coercion in the language can makes this less clear, similar to how it is not obvious from the call site that a function may be taking a c++ reference.

Edited by Caleb on
Bit of a bump, but found this thread on Google. For me personally, I have found just using 0 as the null pointer to be wonderful. Have had zero bugs resulting from this practice (though I work alone, so don't have to worry about "best practices"), and probably saved myself a bunch of typing and mental overhead. Zero is initialization is the new mantra. Thanks Casey!

I like how this understanding makes working with pointers more succinct:

1
2
3
if (ptr) {
  foo(ptr);
}


as opposed to

1
2
3
if (ptr != NULL) {
  foo(ptr);
}


Same deal with the string "null terminator," i.e. 0.
In summary: Having a more unified understanding + intuition about 0 has been really nice.

Edited by poe on
These two if statements do different things. First one calls function when pointer is NOT null. Second one calls function when pointer IS null.
mmozeiko
These two if statements do different things. First one calls function when pointer is NOT null. Second one calls function when pointer IS null.

Whoops, silly typo. Thanks! I think the long descriptive variable names just ended up confusing things XD

Edited by poe on
poe
Bit of a bump, but found this thread on Google. For me personally, I have found just using 0 as the null pointer to be wonderful. Have had zero bugs resulting from this practice (though I work alone, so don't have to worry about "best practices"), and probably saved myself a bunch of typing and mental overhead. Zero is initialization is the new mantra. Thanks Casey!

I like how this understanding makes working with pointers more succinct:

1
2
3
if (ptr) {
  foo(ptr);
}


as opposed to

1
2
3
if (ptr != NULL) {
  foo(ptr);
}


Same deal with the string "null terminator," i.e. 0.
In summary: Having a more unified understanding + intuition about 0 has been really nice.


You can still make the null pointer special when casted to bool, which is what C++ nullptr will do as well.

and zero initialization has more to do with being able to memset the struct to all 0 bits and having reasonable behavior. Which is orthogonal to whether the literal 0 will implicitly convert to the null pointer in the type system