NULL Define in c & c++ differs?!

I'm a bit confused about the NULL constant, while doing this exercise on the k&r book:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/*
this is a "find" program:
usage:
find -options pattern

-x shows the lines without the pattern instead of the one that actually contains it.
-n print line numbers
*/
main(int argc, char *argv[])
{
    while(--argc > 0)
        printf((argc > 1) ? "%s " : "%s", *++argv);
    printf("\n");
    return 0;
}
#endif

#include <stdio.h>
#include "mylib.c"

main(int argc, char *argv[])
{
    char line[MAXLEN];
    long lineno = 0;
    int c, exept = 0, number = 0, found = 0;

    while(--argc > 0 && (*++argv)[0] == '-')
        while(c = *++argv[0])
            switch(c)
            {
                case 'x':
                    exept = 1;
                    break;
                case 'n':
                    number = 1;
                    break;
                default:
                    printf("find: illegal option\n");
                    argc = 0;
                    found = -1;
                    break;
            }
    if(argc != 1)
        printf("Usage : find -x -n pattern\n");
    else
        while(getline(line, MAXLEN) > 0)
        {
            lineno++;
            if((strstr(line, *argv) != NULL) != exept)
            {
                if(number)
                    printf("%ld:", lineno);
                printf("%s", line);
                found++;
            }
        }
    return found;
}


If I use 0(line 49) I get no warning(and that is strange since the book use NULL), if I use NULL(that is what is used in the book, so it should be correct) the compiler says "warning C4047: '!=': 'int' differs in levels of indirection from 'void *'".
I see that NULL is defined as ((void *)0 in c and 0 in c++. But since I'm still Learning and maybe I miss some pieces of the puzzle. I don't get even this cast from a constant, what does it mean? I see that pointers normally get only addresses and 0 is the only exception from that. Hope somebody can unravel this in a simple way. I thought a null pointer can be compared to 0, and that is what the compiler seems to like, so why is NULL ((void *)0?
It was a surprise to find the actually NULL change from C to C++!
Thanks guys!

Edited by rizoma on
C has many mistakes in its history, defining NULL as 0 (an int) instead of a special ident like nullptr (which is what you should use in C++) is one of them.

Why C++ doesn't define NULL as nullptr is because idiot programmers used NULL as the 0 constant, most probably to avoid "magic number" warnings and nullptr doesn't convert to int implicitly so a #define NULL nullptr would break the C compatibility.
Sorry maybe I don't undestand:

ratchetfreak
C has many mistakes in its history, defining NULL as 0 (an int) instead of a special ident like nullptr (which is what you should use in C++) is one of them.

You swapped c and c++?, in that case I get it.

ratchetfreak
Why C++ doesn't define NULL as nullptr is because idiot programmers used NULL as the 0 constant, most probably to avoid "magic number" warnings and nullptr doesn't convert to int implicitly so a #define NULL nullptr would break the C compatibility.

I'm compiling in c, why I get this warning if I compare to NULL(nullptr) and no warning if I use 0?(line 49)

thanks for helping anyway :)

Edited by rizoma on
The reason NULL is used is because of the issue of dangling pointers. You know what pointers are, vaguely?

A dangling pointer occurs when the variable that the pointer points to no longer exists. After that, who knows what it points to. A very unsafe practice.

So, NULL is used. It is defined as (void *)0 because then, the pointer then points to nothing. void is a special type that refers to nothing, but this makes it useful to refer directly to a location in memory.
So, (void *)0 is a pointer to a special memory location, namely 0, that write access is explicitly forbidden to.

Don't use 0 in place of NULL. You can, technically, but it can cause confusion. Using NULL is a much better practice.

Hope that makes some kind of sense for you at the moment. :)

Edited by Kyle Devir on
C++ isn't the only language with mistakes in its design. C has its fair share, among which null-terminated strings and 0 as the null pointer constant.

C++ has created the nullptr keyword which evaluates to a null pointer and can implicitly convert to any type of (raw) pointer. This is exactly what you want in a null pointer constant. However it then didn't #define NULL nullptr like they should have. because it would break C code that used it as a int.
Since you're not including string.h, I guess you're implementing strstr in mylib.c. Does your strstr function returns a char* or a integer ? You can compare a pointer to 0 (only 0, not all integer). If your function returns an integer, you would be comparing it to a NULL which in C in a pointer and so is illegal (but not in C++ since NULL is just 0). I compiled using the string.h version (returns a char*) and there was no error.

I'm not sure, and it would be great to have someone with more experience answer this, but if I remember correctly the reason NULL = 0 in C++ and NULL = (void*)0 in C is because the standards say what they should be. The value in both case is 0 and I guess the generated code is the same. You can find some informations here. Also note that, as far as I know, the visual studio C compiler isn't supporting "pure" C very well.

Edited by Simon Anciaux on Reason: Include brackets "eating" the include.
Ok that is really weird, I forgot to include string.h now the warning is gone.
I really don't want to dig into it anymore...
Thanks everybody for helping!
mrmixer
Since you're not including string.h, I guess you're implementing strstr in mylib.c.

I got no strstr function in mylib.c.
What I found is that before, I was compiling in c and strstr was working without string.h! Now I remember I noticed that even printf works without including any header file in this compiler. So I tried to compile in c++ after I found that NULL are declared in different ways. After that the compiler found errors in mylib.c(not related to this code in particular) and finally I got the unresolved symbol!
So as you said the Microsoft compiler is not good for c.
Now I'm back in c mode and the warning is gone.
Not happy about this at all!
I tried Yesterday some code in visual c++ 1998, and a part of being superfast even on a celeron 500mhz, it asked for stdio.h for printf!, so I assume the old compiler is better at least for C!
Anyway MrMixer thanks for the nice infos I will check that :)

Edited by rizoma on
This issue is not Microsoft compilers fault. This is C standards fault.
If C compiler doesn't see function declared, then it assumes function is declared implicitly with "int" return type. For 32-bit platforms this will work fine. But once you'll compile 64-bit code it won't work always.

As long as function really returns int you'll be fine not declaring functions - like printf in your case. Note though that it implictly defined function accepts variable number of arguments, then behaviour is undefined.

In your case because strstr function is not defined (string.h not included), compiler assumed it returns int. That's why it generated warning when you compare result with NULL which has "void*" type in C.
ratchetfreak
C has many mistakes in its history, defining NULL as 0 (an int) instead of a special ident like nullptr (which is what you should use in C++) is one of them.

Why C++ doesn't define NULL as nullptr is because idiot programmers used NULL as the 0 constant, most probably to avoid "magic number" warnings and nullptr doesn't convert to int implicitly so a #define NULL nullptr would break the C compatibility.


Just wanted to comment for anyone else coming in here... In C NULL is defined as ((void*)0) (by the common compiler vendors), and not as raw 0, which is pretty much identical to the keyword nullptr. So which is better? #define NULL ((void*)0) or nullptr? I don't really think it's fair to call one way or another stupid or idiotic; it's largely a matter of opinion.

For example I have my own opinion: C defining NULL as ((void*)0) was great because anyone can go lookup the exact definition of what NULL is and see the definition in terms of elementary expressions, integers and type-casting. This helps make it obvious that pointers are, on the assembly level, just numbers like any other integer. The concept of a pointer is just a semantic construct. If we look at nullptr, what exactly is it? Well, we don't know without doing a google search or looking up some kind of specification. Suddenly nullptr becomes a little more abstracted when nullptr is used.
Randy Gaul
For example I have my own opinion: C defining NULL as ((void*)0) was great because anyone can go lookup the exact definition of what NULL is and see the definition in terms of elementary expressions, integers and type-casting. This helps make it obvious that pointers are, on the assembly level, just numbers like any other integer. The concept of a pointer is just a semantic construct. If we look at nullptr, what exactly is it? Well, we don't know without doing a google search or looking up some kind of specification. Suddenly nullptr becomes a little more abstracted when nullptr is used.


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.
At the risk of stating the obvious, I strongly disagree with the notion that a pointer's zero value is fundamentally different from an integer's zero value, or an enum's zero value, or anything else's zero value for that matter. Treating zero as a special value which means "uninitialized" or "unknown" is a very powerful construct that prevents a lot of bugs and is, in my opinion, a very good habit to get into while programming. Saying "clear to zero is initialization" is probably one of the most useful programming practices I know!

However, if you instead start pretending that the zero value of different types are fundamentally different, you end up in a world of hurt. You can't "memset to zero" to initialize something, because you're saying it's _not_ zero, right? It's this _other_ symbolic value that we somehow don't know what it is - maybe it's zero but maybe it's something else! It's just "nullptr" as far as we know, and who can say really!

It's all really silly in my opinion. I don't know what bugs people are trying to catch when they use NULL or nullptr - they certainly can't be bugs that are very common, because I can't remember the last time I've had one?

Stated alternately, the type system in this case is strictly an error-checking construct, so in order to start believing that NULL or nullptr was saving you time or energy instead of just introducing another syntactic thing that can cause compatibility problems (such as the OP found), you have to actually believe you make mistakes frequently that this would solve. And I have a _really_ hard time believing that's true - even harder than const, which I think is pretty dubious, but nullptr/null seem doubly moreso.

- Casey
Whether or not the null pointer is represented by 0 is orthogonal to letting 0 implicitly convert to a null pointer.

You can easily have a platform that defines the null pointer as 0xffff_ffff and still be compliant to C. All it means is that when the compiler sees a conversion of 0 to a pointer it instead creates a ~0.

Unless I'm mistaken all that C requires is a single address that is invalid but doesn't put any restrictions on what that address is.

in other words

1
2
3
4
5
6
7
union{
    void* ptr;
    size_t i;
} un;

un.i = 0;
assert(un.ptr == NULL);


Whether the assert triggers is implementation defined. It's just that currently all non-embedded implementations will not make it trigger.
Yes, what I am saying is stronger: 0 _must_ be a reserved address meaning "null pointer", always, in all cases except for the like one singular exception in kernel mode code where you're actually talking about the base of physical memory for some required reason. This is because it is crucial that memset(0) work to clear things, including pointers, and giving up that functionality is much too costly in practice IMO.

- Casey
cmuratori
Yes, what I am saying is stronger: 0 _must_ be a reserved address meaning "null pointer", always, in all cases except for the like one singular exception in kernel mode code where you're actually talking about the base of physical memory for some required reason. This is because it is crucial that memset(0) work to clear things, including pointers, and giving up that functionality is much too costly in practice IMO.

- Casey


I don't disagree with that.

I just have an issue with the fact that "void* ptr = 0;" was ever valid code.