abort() is intended as a last-ditch effort. exit() is the one that attempts to flush all open buffered file descriptors, and should be used in lieu of abort except in cases where you know you're screwed, or explicitly want to throw a signal so a debugger can take a peek.
In practice, <asterisk>(int <asterisk>)0 (how do I escape asterisks on HN?) and similar are popular idioms for "segfault here". Making them break is not an optimization any compiler maintainer would bother to make - it requires a special case and there don't seem to be any benefits to justify the effort.
Plan 9's abort causes an access fault, causing the current process to enter the `Broken' state. The process can then be inspected by a debugger. Pretty elegant.
Not, as it happens, on plan 9 systems where the kernel marks that area as not accessible. This could fail, conceivably, but then you're in much deeper stuff.
gcc, at least, optimises out the null deref for both:-
*(int *)0;
and:-
for(;;)
*(int *)0;
So the first bit of code does nothing, and the second slips off into an infinite loop.
I suspect that treating expressions that demonstrably lack side effects (other than the intended segfault here of course) as statements is undefined, and hence these are getting optimised out (even with -O0).
Clearly with:-
while(*(int *)0)
The expression is being evaluated and is therefore not elided, I guess the choice of while is to 'be cute' as others have suggested, and I guess the world is sane in plan 9 and 0 is readable so you can't get a situation where it escapes the loop. Perhaps there is a deeper reason here that I am missing, however.
(int *)0 is not defined as a pointer to memory address 0x0 on architectures that support such an address.
0 cast as a pointer is defined by the spec to always be the NULL pointer, which on such architectures would have a value other than 0x0 and not point anywhere addressable.
I'm not a plan 9 programmer, but to me it looks 'cute' (in the sense of attractive to some people but annoying to others) - that form of abort() would only be used on systems where that operation is known to abort the process, but enclosing it in a while simply makes it apparent that there is no alternative to trying it.
On a more prosaic note, perhaps
for(;;)
*(int *)0;
generates a compiler warning that the programmer wanted to avoid.
As far as I know, the kernel programs the MMU so that dereferencing 0 will always fault. I could be wrong, as my understanding of the kernel is limited. I am not sure of the purpose of the loop, but to me it make it unavoidably obvious that the function never returns.
I may be wrong, but I thought that in a multithreaded environment, doing i++ is not atomic and could result in garbled data. Instead you should use __sync_add_and_fetch. However, I have no idea if it should be used inside abort().
>I may be wrong, but I thought that in a multithreaded environment, doing i++ is not atomic and could result in garbled data. Instead you should use __sync_add_and_fetch. However, I have no idea if it should be used inside abort().
This is only true if the variable in question's memory is accessed by multiple threads at the same time, and there isn't any locking or synchronization method used to protect the memory.
In this case, even though it is a globally scoped variable, it's locked by the globally scoped mutex declared in the file. All increments are done in the locked sections, so there isn't any possibility of accessing the variable without having a lock.
It should be noted that there is a very minor race condition when abort() is called in two different threads sequentially, and every attempt up to line 89 doesn't work. The first call will get the lock, then go through to line 89, where it released the lock. The second thread will then get the lock, and go through the first section. When it hits the section line 89(if (been_there_done_that == 0)), that will resolve to false, because been_there_done_that is 1. It will then go on, leaving the first thread deadlocked at the LOCK attempt on line 91. This shouldn't result in any missed functionality, but I actually wonder why they're releasing the lock in the first place. Raise() isn't thread safe anyway, because the signal is applied to all threads in the process. Plus, you're trying to suicide the program. It's a bad idea to even have the possibility of multiple threads trying to kill themselves at the same time.
The lock must be released because the same thread might reenter abort() in the signal handler, and without a release in the parent abort(), the program would hang.
Since the lock is not guaranteed in the code, the variable is globally defined and the code only ever increases it. This means a step in the chain of killing could get skipped, but that doesn't matter, as there's always a more violent option (or just the infinite loop).
When I don't care about the result, I always write preinc/decrement too. Sure, it's superfluous on any non-braindead compiler (it should be able to see that you don't care about the result of a postincrement and elide the temporary), but it's just habit at this point. I fail to see how it reduces or changes readability though.
Sounds like you just have an axe to grind with Drepper.
I don't have anything personal against Drepper. I've never had any direct experience with him of any kind.
I thoroughly enjoyed his article about memory. He is obviously an extremely intelligent and knowledgeable guy.
I am afraid that he is too clever by half though, insofar as good code is clean and readable first, and clever second. Every time I've had an opportunity to interact with the glibc codebase I'm dismayed that such an important, core piece of software has been written so cleverly that it essentially can only be maintained by one guy.
It's probably a quip referring to Ulrich Drepper, a kernel hacker whose personality seems to be quite controversial according to a quick Google search. I'd love to hear the GP explain it further though.
And also not very robust. Sure, it'll halt execution -- of that one thread, anyway -- but if the program has a SIGABRT handler installed that doesn't exit, abort() will fail to do its job. It'd be nice to try just a little harder to kill the program.
the one in line 87. It looks like it might be added later because it's not even indented, but clearly it doesn't serve any purpose and it doesn't make it more readable (you can tell the function never returns from the second while(1) loop).
http://news.ycombinator.com/item?id=2607116
Interesting how fundamentally simple tasks like aborting a process or rebooting the machine have very nontrivial (even kludgey) implementations.