9 ms·
I don't get the buzz about null pointers. They are not a problem. They become a problem when you start checking for null where null is not acceptable (most of t
by jstimpfle 9y ago
I don't get the buzz about null pointers. They are not a problem. They become a problem when you start checking for null where null is not acceptable (most of the places null can't be a meaningful input), which is where is where the original intent starts to become unclear.
Let it go. Let it crash. Just assume inputs are non-null (except where null makes sense). Even C crashes safely on null pointer dereference.
- IshKebab 9y agoThe problem is that the type system doesn't encode whether a point can be null or not, so unless your documentation is amazing (I doubt it) you'll eventually end up getting passed a pointer from someone else, or giving a pointer to someone else who has a different assumption about whether null is allowed. Boom segfault. There are several solutions in C++. 1. Always check for null. Kind of annoying and lots of people don't for whatever reason. 2. Use references. As you say, annoying because then you can't have null (sensibly) even when it would be really useful. 3. Use std::optional<int&> or something like that. I only just thought of this and don't know if it would work, but I bet it's a pain. So no great solution. Personally I would use a smart pointer type, document it as well as I can, and always check for null.
- gpderetta 9y ago> Always check for null. Kind of annoying and lots of people don't for whatever reason. use assert or some custom always-assert-even-on-release macro. > Use references. As you say, annoying because then you can't have null (sensibly) even when it would be really useful. well, only use references for non nullable pointers. Otherwise use plain (or smart) pointers. > Use std::optional<int&> or something like that. I only just thought of this and don't know if it would work, but I bet it's a pain. std::optional<T&> is unfortunately not part of C++17, mostly because people couldn't agree on operator= semantics: int x = 1; int y = 2; std::optional<int&> oint = x; oint = y; // which one is true? assert(x == y); // deep assign assert(&*oint == &y); // rebind boost::optional supports references and IIRC rebinds on plain assignment.
- smelterdemon 9y agoFor parameters where you aren't transferring ownership, just use raw pointers/references. If it's a required parameter make it a reference. If it's optional, use a pointer and check for null.
- pjmlp 9y agoThe problem in C derivative languages is that it isn't enough. There is no guarantee that the pointers are valid, even if not null. So besides checking for null, to be correct we need to call OS APIs to check for pointer integrity as well.
- smelterdemon 9y agoThat's on the client code. You can't stop callers from doing something stupid (or cosmic rays from flipping bits). At that point crashing fast is preferable.
- pjmlp 9y agoI disagree, the premise of design-by-contract and clean code methodologies is that functions must ensure the integrity of the data handled by them.
- jstimpfle 9y agoThat's theoretic talk. Try doing it in practice and still get things done / be able to maintain the code / see the forest for the trees. But I have a feeling that we are lacking a bit of context here. Some people seem to focus on web-application style of programming (understandably) where you have lots of trust issues. Whenever data is carried across trust boundaries it needs to be checked (this applies to integrity in general, of which null safety is just a small part). (On the other hand, deserialization is not about validation of function arguments. Deserializers should assert integrity on the spot before calling into deeper nested functions).
- 9y ago
- jstimpfle 9y ago> so unless your documentation is amazing As I pointed out I usually don't have null pointers at all. There might be a few places where a value can very obviously be null, but they are far and far between. No need for documentation. > Boom segfault. Which is exactly the right thing to happen (it's the C version of Exceptions in dynamic languages) since the code was incorrect. > Always check for null. Kind of annoying and lots of people don't for whatever reason. And now what do you do if you detected null but it was not allowed? Throw an exception? You can have that for free by just not checking. > Use references. As you say, annoying because then you can't have null (sensibly) even when it would be really useful. C++ references don't really protect you from NULL. It's just a different syntax for the same thing. > Use std::optional<int&> or something like that. I only just thought of this and don't know if it would work, but I bet it's a pain. Yes, pain, big pain. So much line noise and typing (in both senses) around what is essentially an int. Just don't check. I don't get why there's so many places for null pointers. (As I said elsewhere, I don't have a solution for JSON style microscopic programming, and I don't care about that.).
- palunon 9y agoHow do C++ references not protect you from NULL ? From the standard: > A reference shall be initialized to refer to a valid object or function. [Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by dereferencing a null pointer, which causes undefined behavior. As described in 9.6, a reference cannot be bound directly to a bit-field. ]
- jstimpfle 9y agoBetter try instead of reading standards (and inevitably misreading, or reading differently than compiler authors)... #include <stdio.h> void test(int& x) { printf("Hello, world\n"); fflush(stdout); printf("and the number is: %d\n", x); } int main(void) { int *x = NULL; test(*x); return 0; } It's just a syntactic discipline. Null references are undefined in C++, just as NULL dereferences are undefined in C.
- dan00 9y ago> Let it go. Let it crash. Just assume inputs are non-null (except where null makes sense). Even C crashes safely on null pointer dereference. If the amount of work done before the crash has some value and is lost by the crash, then crashing just isn't an option.
- jstimpfle 9y agoWhat everybody is missing here is that null pointers where none was expected are just an instance of wrong code. How do you protect yourself from wrong code? (Hint: not by adding more code!)
- dan00 9y agoPerhaps you're just missing the complexity of the real world? I'm working on quite big finite element desktop applications, where alone the reading of the data can take up quite a bit of time. The user can then apply all kinds of different operations on this data and if one of these operations fails because of a null pointer - then sure, the code is wrong - but nevertheless the user doesn't want to loose all of his previous work and wants to be able to save the changed data. Sure there're cases that a failed operation might have corrupted the data, but you just can't tell this in every case, and often the data is still valid and only the operation couldn't be applied. If I've learned something over the years, then that there's not one solution that works for all cases.
- jstimpfle 9y agoSo you weren't expecting bad user input? Bad for you! This is called validation, and in a validation routine you expect bad invalid data. Check inputs at the trust boundaries, and off you go. Note this is NOT about just null pointers but about integrity in general.
- dan00 9y agoWell, if you want a serious discussion then don't imply something just to be able to make a point. But if you just want to win an argument: here you go.
- lmm 9y ago> They become a problem when you start checking for null where null is not acceptable (most of the places null can't be a meaningful input), which is where is where the original intent starts to become unclear. The trouble is it's not always clear whether null is a meaningful value or not. The type of an expression should tell you what values it might take - that's the whole point of a type. Having the same type for pointers that could be null and pointers that will never be null is like having the same type for integers and floating-point numbers. > Even C crashes safely on null pointer dereference. False. A null pointer dereference in C is undefined behaviour and can therefore easily be a security flaw (there's been at least one local root vulnerability in Linux due to this).
- jstimpfle 9y agoIn database speak, if your function takes nullable values it is "denormalized". If you design your data structures and flows right, you really don't have many of these. > False. A null pointer dereference in C is undefined behaviour and can therefore easily be a security flaw (there's been at least one local root vulnerability in Linux due to this). Yes. in theory... and yes I heard there are also some stupid compiler optimizations around this. Still, the point is, what do you get from checking for a condition that shouldn't happen? At most an assert is justified! You don't check for the infinitely many other conditions that shouldn't happen, either.
- lmm 9y ago> If you design your data structures and flows right, you really don't have many of these. So it's really important to be able to tell which ones they are! > what do you get from checking for a condition that shouldn't happen? At most an assert is justified! An assert is a check, still takes up a lot of reading time if you have to do it for every parameter on every function. > You don't check for the infinitely many other conditions that shouldn't happen, either. I check for all the things that the code allows to happen, because sooner or later they will happen. You have to do this if your code ever accepts user input; any input that is possible will, sooner or later, find its way in. So if something shouldn't happen the best course is to make it impossible in the code.
- pjc50 9y ago> Let it go. Let it crash It must be nice to work in an environment where nobody cares about results.
- cytzol 9y ago> It must be nice to work in an environment where nobody cares about results. Not crashing when there's a bug is not "success", it's just "zero visible errors"! If your workplace really cares about results, they'll understand how futile it is to keep executing even though there's a bug. You start second-guessing your own program, which makes the code quality worse as the assumptions it makes are no longer clear; you won't be alerted to problems, as the program won't be aware of any; there'll be no guarantee that your code isn't just working "by accident" at the moment. I'd love it if the code I deal with just crashed instead of pretending everything's alright, because it makes errors far harder to diagnose.
- hamstergene 9y agoNo one is talking about keeping execution instead of crashing. C++ smart pointers do not do that, optional types do not do that, no programming language whose creators are in their right mind has ever pursued that. You have just produced a classic example of a straw man argument.
- cytzol 9y ago>> Grandparent: "I think you should just let your code crash." > Parent: "I think that is a bad idea." It seems pretty clear that we're talking about crashing here.
- hamstergene 9y agoThere is a third option to crashing vs. not crashing: not letting the code compile if there is any chance it might crash. That is what this "buzz about null pointers" is about.
- simias 9y ago>Even C crashes safely on null pointer dereference. Uh, that's a rather optimistic way to look at this. In C NULL pointer dereference is UB so you shouldn't rely on this behaviour. For one thing the compiler is allowed to do weird things if it can statically assert that a NULL reference takes place, for instance by dropping any code that can be proved to lead to a NULL dereference. Not so safe or friendly. Most of the time for userland apps dereferencing a NULL pointer for reading or writing will cause a segfault but that might not be the case for bare metal/MMU-less applications. Furthermore even when it does crash it simply terminates the process without any unwinding which might leave non-volatile data in a non-consistent state. The real problem with pointers is that they're effectively an algebraic data type, they can point to something or be NULL. But the type system can't enforce that, there's no concept of non-nullable pointers in C. Take the prototype of nanosleep(2) for instance: int nanosleep(const struct timespec *req, struct timespec *rem); Turns out that the 2nd parameter is NULLable if you don't care for the reminder. The first one isn't however, so passing NULL there is a big no-no. In rust the equivalent signature would be something like: fn nanosleep(req: TimeSpec, rem: Option<&mut TimeSpec>) -> Result<(), i32>; Which is a lot more explicit (mind you, that's probably not what the API of nanosleep would look like in rust since output parameters are not very common, you'd probably return rem in the Result). The problem is the same on the library side. When you're passed a pointer, should you assume it's nullable? Should you check for it? If you want to be safe you do, and you end up with a bunch of redundant checks. It's also easy to end up with a NULL-ish pointer that's not NULL if you happen to offset a NULL pointer by mistake (like a NULL array or NULL struct pointer). That can be pretty tricky to track down since the crash doesn't occur when the offset is calculated but rather when the bogus quasi-NULL pointer is dereferenced: #include <stdio.h> struct s { int a; int b; }; void bar(int *i) { if (i == NULL) { printf("NULL\n"); } else { printf("%d\n", *i); /* i is invalid but not NULL, this (probably) crashes */ } } void foo(struct s *ps) { int *b = &ps->b; /* ps assumed not-NULL here */ bar(b); } int main(void) { foo(NULL); /* This is where the actual error takes place, foo's argument is not NULLable */ return 0; } Also consider what happens if we're using a NULL pointer to a very large buffer. Dereferencing the beginning of the buffer would probably crash but higher addresses might end up in mapped pages and you start accessing and possibly overwriting random parts of the runtime.
- titzer 9y ago> Let it go. Let it crash. Just assume inputs are non-null (except where null makes sense). That's just asking for a disaster. Null pointer dereferences are undefined behavior in C, and compilers can and do make these assumptions, leading to all kinds of horrors. (https://software.intel.com/en-us/blogs/2015/04/20/null-pointer-dereferencing-causes-undefined-behavior https://software.intel.com/en-us/blogs/2015/04/20/null-point...)
- DamonHD 9y agoNot in many important environments, eg especially those without virtual memory where the zero page can be mapped invalid. Talk to me about how good an idea it is not to check potentially-NULL pointers in embedded systems.
- jstimpfle 9y agoSure I know about that... I don't do embedded, but the point is, if your program is wrong, it goes wrong. So? Fix it!
- DamonHD 9y agoAn embedded program using NULL may cause some serious damage, even physical. Code has bugs: avoiding the most serious failure modes is important pragmatically speaking.
- alkonaut 9y ago> most of the places null can't be a meaningful input "Maybe something" is not rare. In fact it's often just as common as "Definitely something" in my experience. A customer maybe has a shipping address. You could bend over backwards and say that either he has an address or else the address reference must point to the special no-address value instad of being null. But in that scenario you still need to check for whether it's the no-address or an actual adreess. Otherwise you might end up doing things like trying to set the street address of the no-address value which could either throw an exception or (worse) suddenly everyone without address lives at that address. So the only real solution is to use proper types to model "maybe something". You can use a list of addresses for the customer. That naturally supports 0..N. Or you could use a proper option type (which is just a collection with max count 1).
- jstimpfle 9y agoData design problem. C / pointer style is not good at this JSON-structs style of programming. I agree that algebraic (like Haskell's) datatypes could help ruling out some invalid objects here. On the other hand, you can reap huge benefits from data-oriented programming (basically, make a separate email table that has a "foreign key" to the customer table) to do processing in the large. This is where C is a very good fit and you have no null pointers :-)
- dkersten 9y ago> Even C crashes safely on null pointer dereference. Does it? What if you needed to clean up external resources? What if some data was left in an inconsistent state? What if you were in the middle of writing to a file? Etc. I do agree that crashing is often the right thing to do and of course the machine can die anyway, but sometimes you want to at least attempt to recover. Also, in much of the software I've written in recent years, crashing was not acceptable because just because one thing (eg request of some kind, message, task, whatever) failed unexpectedly, does not mean that others will too and we still want others to be attempted. Sometimes you can get away with letting the program crash and then restarting it to run the other work, but often things are done concurrently and restarting would lose any in-progress state. If the work takes a long time, restarting all work because one piece failed is often unacceptable.
- jstimpfle 9y agoYes, considering communication heavy architectures, it's unfortunate that with C the unit of encapsulation is the process. And there's bad support for inter-process communication, etc. Maybe have a look at Erlang? I suppose it's more aligned with what you want to do. There you also "crash", but only managed, green, processes with very convenient communication channels.
- dkersten 9y agoYes, indeed. Sadly I’ve never had the opportunity to use Erlang for work, I’d certainly like to give it a try. I suppose I should try it (or elixir) on a side project sometime since it’s always a bad idea to go blind.