8 ms·
nullptr has been such a Godsend for C++. Good to see it coming to C. If you ever see the macro NULL in code, be afraid. There are two valid ways of defining th
by cjensen 3y ago
nullptr has been such a Godsend for C++. Good to see it coming to C.
If you ever see the macro NULL in code, be afraid. There are two valid ways of defining the macro and that cause weird issues when porting code. For example, in the statement printf ("%p %s\n", NULL, "Hello world!"), one of the definitions leads to NULL being interpreted as the null pointer, and the other leads to NULL being interpreted as an integer. The latter may crash if integer and pointer are different sizes.
It also causes problems with C++ overloading if one overload takes a pointer and another takes an integer.
- LordShredda 3y agoBut does C need a nullptr keyword? If you're programming in C, you usually define 0 as an invalid value, or a null value. C doesn't have the insane type system C++ has and doesn't have a very strong need to make a distinction between a pointer or an integer, since they're all in the end numbers. The printf example you gave is an example of garbage in, garbage out. If NULL is a macro not defined as a pointer sized integer, then you're at fault here.
- LegionMammal978 3y agoWell, the fault depends on who "you" are: the NULL macro generally comes from one's libc, and allegedly some libc maintainers have been very obstinately against changing their NULL macros to have pointer type.
- throwway120385 3y agoAren't there platforms where pointers have additional type or space information encoded that is orthogonal to the numeric address? It's only by convention that NULL == 0 because on platforms like Intel & ARM you would typically not use the first page. But that's only a convention, and you could just as easily put a null page at the top of your address space, especially in systems with an MMU where mappings can be added, removed, or remapped as-needed.
- LegionMammal978 3y agoIn C, the integer 0 is explicitly defined to convert to a null pointer for all assignments, casts, comparisons, etc., regardless of what the pointer's "actual" value is. The only time where you can see that a null pointer doesn't have numeric value 0 is when you manipulate its object representation with memset, memcpy, etc. The compiler is also at liberty to return whatever it wants when you convert a null pointer to an integer, except that converting it back must produce a null pointer (if it's at least as wide as intptr_t).
- mananaysiempre 3y ago> It's only by convention that NULL == 0 [...] and you could just as easily put a null page at the top of your address space [...]. Technically NULL == 0 always because the standard special-cases zero-valued integer constant expressions; (uintptr_t)NULL == 0 or NULL == *(void **)calloc(1, sizeof(void *)) is another matter :) Language lawyering aside, a non-all-zeroes representation of NULL will probably blow up most C programs [e.g. static-storage-duration initialization is now not the same as calloc or memset(,0,) and is even type-specific]. Like CHAR_BIT, that’s a joint that technically exists but has been rusted for decades (pun not intended).
- kazinator 3y agoThere is no problem with static initializations with a null pointer that is not all zero bits, or a floating-point 0.0 that is not all zero bits. Those values just cannot participate in the "BSS" trick, whereby everything that is zero-initialized is put into a special section that doesn't actually exist in the program image, and is only provided on startup. Those values would go into the initialized data section. The problem with 0.0 or null pointers not being all zero bits is all the code that uses calloc or memset zero. If this is on some specialized platform (e.g. DSP chip), it might not matter that vast quantities of C code are not portable. In general, compiler (and to a great extent instruction set architecture!) designers are quite hamstrung by the expectations of C programmers and programs; that has been the situation for some thirty years now. Today, you could not sucessfully introduce a system in which pointers to bytes (void , char ) have a different representation from other pointers (let alone different size, lord forbid).
- mananaysiempre 3y ago> If NULL is a macro not defined as a pointer sized integer, then you're at fault here. If it was you who wrote stdlib.h, sure; otherwise, if you’re on a platform where NULL is traditionally defined as 0 and not (void *)0, you’re stuck. A conformant implementation is free to use either definition. If you want to language-lawyer more heavily, C does not require there to be pointer-sized integers (uintptr_t is optional), does not require that all zero bytes represent a null pointer in memory (unlike for integers), does not require that the implementation choose to store an integer with value zero as all zero bytes (there may be other valid representations), and in any case does not require an implementation to do anything reasonable at all if the caller passes an integer but a vararg callee looks for a pointer (think separate integer and pointer registers). [I’m not entirely sure if (void *)(void *)0 is a null pointer constant (though it’s certainly an expression that evaluates to a null pointer)—does it count as a zero-valued integer constant expression cast to a pointer to void? So you might not even be able to use (void *)NULL as a hedge against bad platform headers.]
- kazinator 3y agoYou're allowed to do: #undef NULL #define NULL ((void *) 0) Just don't do it prior to the inclusion of any standard header (C or POSIX).
- mananaysiempre 3y agoI don’t think you are? Redefining a reserved identifier is UB per ISO C (any version) 7.1.3p2, and per 7.1.3p1, > Each macro name in [the standard library] is reserved for use as specified if any of its associated headers is included; unless [you’re #undef’ing a function also provided as a macro]. The general idea seems to be that standard headers are allowed to use macros they define, even in other macros they define, and because macro names are late-bound (ugh), even if the user only redefines the name afterwards, every macro that uses it will then be affected. As a silly example, a valid part of stdlib.h could be #define NULL 0 #define EXIT_FAILURE (NULL) #define EXIT_SUCCESS (EXIT_FAILURE+1) and now after your redefinitions EXIT_SUCCESS becomes a constraint violation. (For an implementor to actually do this would of course be dumb, but you did say “allowed”, and that’s what the standard says here.) Or did I misunderstand “use as specified”?
- kevin_thibedeau 3y ago0 never should have been overloaded in C to refer to the NULL pointer. With pointer assignment and comparison it transforms to the platform's encoding for NULL which isn't necessarily all zeros. No other literal has this sort of magic.
- WalterBright 3y agoThis works in Standard C: int *p = 3;
- kevin_thibedeau 3y agoThe address is still 3 which has valid applications. C is permissive enough to run on platforms that don't use address 0 for NULL. With pointer operations the compiler will change the encoding from 0 to that platform's NULL address. int *p = 0; intptr_t i = (intptr_t)p; if(i == 0) ... // Isn't always true
- LegionMammal978 3y agoThat's a compiler extension. In C17, 6.5.16.1 (Simple assignment) implies that the RHS of an assignment to a pointer must either have pointer type or be a null pointer constant (i.e., an integer constant equal to 0, or such a constant casted to pointer type), and 6.7.9 (Initialization) states that "the same type constraints and conversions as for simple assignment apply" to expressions used as initializers.
- Kamq 3y ago"standard" C implies default to me, so the post is probably referring to C89 or C99, which are significantly more used than C17.
- LegionMammal978 3y agoThis particular rule is essentially unchanged from C89 (3.5.7 for initialization, 3.3.16.1 for simple assignment) and from C99 (6.7.8 for initialization, 6.5.16.1 for simple assignment). Generally, most existing rules don't change much at all across the C Standard versions.
- bluGill 3y agoAssuming 0 is an invalid value is not always correct. 0 is a perfectly valid pointer, and making it impossible to refer to that location is bad. Of course if you are not writing an OS or embedded system you won't ever have a pointer value of 0 anyway as the OS can put things elsewhere with no problem (if you are you need to see your CPU docs, some CPUs 0 is invalid, some it is not).
- kazinator 3y agoUmm, no. 0 is the null pointer constant, same as nullptr. It is not a location, but an abstraction. If a platforms null pointer happens to be the address 0xFFFFFFFF, then 0 will produce that. There is no difference between char *p = nullptr; and char *q = 0; other than the variable name; the two have to compare equal: (p == q). What's wrong with 0 is that when it's not in an expression where it's being converted to integer type, it's just an integer.
- bluGill 3y agoThe problem is if 0 is a valid pointer and I write volatile int* x=0; x=0x1234; Did I just deference the null pointer or make a valid write to that memory location? There is no way to know for sure, you can only apply heuristics to make a guess. Of course if the lines are that closely spaced you can guess, but in real code they can be in different translation units.
- kazinator 3y agoISO C says that x isn't a valid pointer for dereferencing, so *x = 0x1234 is undefined behavior. If in your environment that pointer refers to some location where you can put a value, and this is documented, then you're using a documented extension. If you're using some environment in which x isn't the zero address, but you do want the actual zero address, you need some other way to obtain it, like converting a non-constant expression: const int x = 0; char *zeroaddr = (char *) x; C implementations are not required to diagnose null dereferences. In many familiar environments, its arranged by the way the program is loaded. An unmapped page of virtual memory is put at that address. That scheme can be defeated if an offset is involved ptr[large_offset] or ptr->member where member is at a large offset into the struct type. It's possible to have run-time checks for a null pointer as a compiler option, with a run-time penalty. Other than that, you can use assertions to defend against them.
- roqi 3y ago> But does C need a nullptr keyword? Yes, it does. > If you're programming in C, you usually define 0 as an invalid value, or a null value. That was also the usual pattern in C++ when there was no alternative. Once nullptr was introduced in C++, NULL or 0 quickly became a code smell. > C doesn't have the insane type system C++ has and doesn't have a very strong need to make a distinction between a pointer or an integer, since they're all in the end numbers. C++'s type system is far from insane. It's actually one of it's killer features. You're both entirely oblivious to the need to not conflate pointers with integers and failing to present any case in favour of the legacy and broken use of NULL, and in the process failing to address all family of known error patterns involving it. > The printf example you gave is an example of garbage in, garbage out. If NULL is a macro not defined as a pointer sized integer, then you're at fault here. Again, you seem to be completely oblivious to the problem domain. NULL is not a macro as far as C or C++ compilers are concerned. NULL is a magic constant that's resolved at preprocessing time. Replacing NULL with nullptr means a magic constant is replaced by a concrete type, and thus whole family of errors can be avoided with compile time checks. Claiming that the developers who wrote in bugs are at fault for inadvertently adding bugs makes no sense at all because it does not solve any problem at all, and instead is just cynical finger pointing. I take compile-time checks over unhelpful finger pointing all day every day.
- colonwqbang 3y agoNULL is a macro. The original mistake by the standards committee was allowing implicit conversions from integer to pointer. I.e. allowing NULL to be defined as simply 0. If NULL had been defined always as ((void *) 0) then I don't see that we would have had a problem. But that's all history now and in this situation I can see that adding nullptr becomes a reasonable way out. It's ironic though that the fix for the different ways to write null is to add yet another way.
- roqi 3y ago> NULL is a macro. You're missing the whole point. As per the C standard, NULL is an implementation-defined null Pointer constant. Macros are resolved in the preprocessing step. The compiler does not know what a macro is. What the compiler knows is whatever the preprocessor passes off in place of the macro. This means the compiler only sees a constant, and has no way to tell what that constant means. If instead of passing random pointer constants you pass an actual type, now the compiler can tell more things. > If NULL had been (...) Irrelevant. The whole point is that it wasn't the committee looked at the problem, and it determined that using a dedicated type is safer, more powerful, and more elegant than passing magic numbers around.
- josefx 3y ago> The latter may crash if integer and pointer are different sizes. Apparently some compilers specified a special __null extension to handle that case before nullptr was a thing.
- kazinator 3y agoSince NULL expands to an implementation-defined null pointer constant, it is valid for those implementations to go as far as #define NULL __null.
- kazinator 3y agoThe problems are that: - NULL is idiomatic: using NULL is entrenched in C programming and it is not going away. - In spite of nullptr existing now, NULL is still (quite stupidly) not required to just expand to nullptr, but to an implementation-defined null pointer constant, rather than #define NULL nullptr. (According to the N2596 draft). - They had over 30 years to tighten the requirements on how NULL can be defined; what's the matter? C99 could already have required NULL to be ((void *) X) where X is an integer-typed constant expression evaluating to zero. I'm not going to start using nullptr. It's not idiomatic C. I'm going to hold out hope that NULL will be fixed so that it expands to nullptr. -- Also, it's possible for a compiler to diagnose when a constant, zero-valued expression is used as the argument of a variadic function. The diagnostic can be confined to cases when such a constant expression is the result of macro expansion: printf_like_function("fmt ...", ... 0, ...); // OK #define FOO 0 printf_like_function("fmt ...", ... FOO, ...); // compiler diagnostic printf_like_function("fmt ...", ... (int) FOO, ...); // OK
- bigbillheck 3y ago> NULL is idiomatic It is today, but idioms are a human concept and who knows how things will be in 2033?
- kllrnohj 3y agoWell hopefully at some point we'll stop writing C at all
- bigbillheck 3y agoInshallah some day, but I don't see it happening in what's left of my career.
- pjmlp 3y agoOnce upon a time K&R C function declarations were idiomatic, in C23 they are out.
- cogman10 3y ago
- ori_b 3y ago> If you ever see the macro NULL in code, be afraid. There are two valid ways of defining the macro Not on a Posix system, where the only valid definition of it is `(void*)0`. C could have adopted this definition. Nullptr is needed in C++ because `0` is the only definition of `NULL` that works with the type system, due to the lack of implicit `void*` conversions. C doesn't have this problem. Adopting the Posix definition of NULL in the standard would have been sufficient -- and unlike `nullptr`, would have solved bugs in existing programs.
- blackpill0w 3y ago>C doesn't have this problem I don't think a strong type system is a `problem`, implicit conversions can lead to so many annoying and hard to find bugs.
- ori_b 3y agoThat's a fine general sentiment. However, in this context it's a problem if you want to assign NULL to a pointer without a cast, which is why C++ added the magically convertible nullptr in addition to the magically convertible `0` constant. char *x = 0; // ok in C and C++ char *y = (void*)0; // ok in C, error in C++ char *z = nullptr; // ok in C++ therefore: #define NULL ((void*)0) // Required by Posix C, invalid C++ #define NULL 0 // Pre-nullptr, the only valid C++ definition C++ can't define NULL the safe way that Posix C does. I don't understand why it's more acceptable to allow magic `0` conversions than magic `(void*)0` conversions, given that the latter is far less likely to happen by accident -- but here we are.
- rightbyte 3y ago> I don't understand why it's more acceptable to allow magic `0` conversions than magic `(void*)0` conversions In the end you don't have to chose between '0' and 'nullptr' anyways. char *x = (decltype (nullptr)) 0;
- kzrdude 3y agoCase in point, integer arithmetic in C. Reasoning about types there is just tiring.