For someone who codes for a living and would be inclined to not check `fork` for an error code, would you mind sharing a bit about why you use that approach?
For someone who codes for a living and would be inclined to not check `fork` for an error code, would you mind sharing a bit about why you use that approach?
I don't - the default behavior of simply ignoring I/O failures if, say, stdout's pipe was broken is usually what I want. In fact, I've had bugs in exception throwing languages where such stdout write failures threw, and I failed to explicitly catch and ignore them.
I also may skip error checking malloc. A null pointer exception / sigsegv / access violation "must" be fine if malloc is failing - even if I handle it nonfatally in our code, some of our closed source middleware doesn't, and neither do some system libraries. At best I can make slightly better fatal error messages for a subset of the resulting failures. If I'm trying to build super reliable software, I need to avoid exhausting/fragmenting memory badly enough for malloc to fail in the first place.
I have a decent chance of checking fork() for failure as I'm on the more paranoid end of error checking. I've seen enough weird junk like SetCurrentDirectory on a real directory failing due to NTFS filesystem corruption leading to an infinite loop - that I assume all documented error conditions will eventually occur somehow, as well as some undocumented ones.
But I've never seen it fail, and I'm probably just going to make it a fatal error.
I rarely use `malloc`, but I typically do check its return code. That's because in my experience, hunting down the root cause of segfaults can be a hassle, so I prefer to write code that will fail fast on failed memory allocations.
> That's because in my experience, hunting down the root cause of segfaults can be a hassle, so I prefer to write code that will fail fast on failed memory allocations.
This is a great reason to add error checking.
I've spent enough time hunting down harder dangling pointers and memory corruption bugs that alloc-returned-nullptr segfaults are trivial/second nature for me to figure out 99% of the time.
There is that remaining 1% of cases, for the already rare scenario where malloc has failed. Quite niche... but sometimes motivation enough for me to go through with adding error checking anyways. Especially if I actually encounter it.
If a failure would lead to data corruption, then yes. That this is important is made clear with gcc+glibc's _FORTIFY_SOURCE macro, which (e.g.) forces you to check the return value of sprintf() among others.
Of course, it's hard to imagine a case where printf() is part of a data pipeline that could be corrupted. So, nominally the answer is a simple `no'.
But it's a bad question, since fork() and printf() failures have different consequences. Also, boilerplate code for printf() never shows using the return value, whereas boilerplate for fork() always shows that. Your malloc() example is much better.
If malloc() failure leads to corruption, you'd better check the return value. Otherwise, sure, ignore it and let your prog just die is fine! Until it isn't ...
It's worth noting that "printf" specifically does not appear to be affected by _FORTIFY_SOURCE if the documentation I'm reading is to be believed.
> But it's a bad question, since fork() and printf() failures have different consequences.
Pointing out there are differences - via visceral example that likely hits close to home in your own practice as a programmer - is the entire point of the question, so I'll call it a good one ;)
The implicit point I'm making is that it's more complicated than "all syscalls must have error handling!" - which raises the question of, well, what does drive the choice anyways?. Ideally, perhaps a carefully reasoned consideration of the differing consequences you've pointed out. Or perhaps more likely, an adherence to examples and inertia. Or perhaps even more likely, a gut call based on things like "can I even think of when this might fail?", which one might answer "no" to - for having not yet developed a sufficiently mischevious imagination.
Regardless - having established the possible existence of nuance - this then lets us properly examine if our documentation does indeed properly prepare us to make these decisions, and how useful posts like the article might be for filling in where our documentation may have failed us.
> Also, boilerplate code for printf() never shows using the return value, whereas boilerplate for fork() always shows that.
Boilerplate for fork() does not always show that. As a concrete counterexample, the first google hit for "fork example" lacks error handling in any of their examples, although they at least mention the possibility of negative return values. I'll avoid linking it in an attempt to avoid improving it's undeservedly high search ranking, but if you're performing the search yourself, it's on the "geeks for geeks" website.
Maybe most sources of proper official documentation get it right these days, but I'm hesitant to assume even that.
> Your malloc() example is much better.
malloc vs fork failures also have different consequences. malloc failing is pretty unlikely to result in killing other processes (like your watchdog process!) for example. So perhaps it's just as bad of an example ;)
Usually when I write code, I assume it's my job to ensure the code works as designed in all reasonable circumstances. That requires understanding the failure modes of any external code I'm calling, at least as far as if/how it could fail.
IIUC, your approach is more like what I'd call prototyping. I.e., you accept that the code might be wrong, but you prefer to deal with that if/when it comes up in testing, so that you can move faster up-front.
Is that how you reason about it?
My suspicion (and naive hope) is that most people would not ignore that case if they knew it was dangerous. But I would expect them to ignore it if they didn't know it existed. Hence this article, I guess, which serves to remind everyone that it can in fact happen and is quite dangerous (due to a potential later interaction with kill).