5 ms·
The most common way to indicate that an error has occurred in a C function is to return non-zero. If this is done consistently, and return values are always che
by eliteraspberrie 13y ago
The most common way to indicate that an error has occurred in a C function is to return non-zero. If this is done consistently, and return values are always checked, an error condition will eventually make its way up to main, where you can fail gracefully.
For example:
int a(void)
{
...
if (oops) {
return 1;
}
return 0;
}
int b(void)
{
if (a(...) != 0) {
return 1;
}
return 0;
}
int main(void)
{
if (b(...) != 0) {
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}
(This means that void functions should be avoided.)
- clarry 13y agoWhat's the point of propagating all errors way up to main if you're only going to exit anyway? I think we know how to indicate errors in C functions. What to do about specific errors, in this case allocation failures, is a more interesting question.
- joshguthrie 13y agoTo make sure you free() all your previous allocations on the way down. You can choose not too, it's "kinda" the same (can't remember the exact difference) but it's dirty and people with OCD just won't accept it. (Disclosure: I got OCD too, this is not meant to make C development hostile to people with OCD.)
- scott_s 13y agoIf your program is going to exit anyway, there's no need to call free() on allocated memory. The operating system will reclaim all of the memory it granted to your process when it exits. Remember that malloc lives in your process, not the kernel. When you ask for memory from malloc, it is actually doling out memory that you already own - the operating system granted it to you, when malloc requested it. And malloc requested it because you asked for more memory than it was currently managing. If your intention is to continue running, then of course you want to call free() on your memory. And this certainly makes sense to do as you exit functions. But if you're, say, calling exit() in the middle of your program, for whatever reason, you don't need to worry about memory. Other resources may be a problem, though. The the operating system will reclaim things it controlled, and granted to your process - memory, sockets, file descriptors and such. But you need to be careful about resources not controlled by the operating system in such a manner.
- joshguthrie 13y agoMultiple reasons. Some kernels may not get memory back by themselves and expect each application to give it back. We're lucky that the kernels we use everyday do, but we may one day have to write for a target OS where it's not the case. Just hoping "the kernel will save us" is a practice as bad as relying on undefined behaviors. If you're coding correctly, you have exactly as much malloc()'s as you have free()'s, so when rewinding the stack to exit, your application is gonna free() everything anyway. Speaking of resources, what about leftover content in FIFOs or shm threads that you just locked? And when you got OCD, you're only satisfied with this: $ valgrind cat /var/log/* ... ==17473== HEAP SUMMARY: ==17473== in use at exit: 0 bytes in 0 blocks ==17473== total heap usage: 123 allocs, 123 frees, 2,479,696 bytes allocated ==17473== ==17473== All heap blocks were freed -- no leaks are possible ==17473== ==17473== For counts of detected and suppressed errors, rerun with: -v ==17473== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 4 from 4) (cat was an easy choice and all I got on this box, but I've had bigger stuff already pass the "no leaks are possible" certification)
- scott_s 13y agoFirst, you sometimes do want to call exit deep within a program. That is the situation I am addressing, not normal operation. Of course you want to always free unused memory and prevent memory leaks. I am quite familiar with the importance of memory hygiene, and have even written documents admonishing students to use valgrind before coming to me for help: http://courses.cs.vt.edu/~cs3214/fall2010/projects/esh3-debugging.php http://courses.cs.vt.edu/~cs3214/fall2010/projects/esh3-debu... Second, please re-read my last sentence. I specifically addressed things that the kernel does not reclaim. This would also include System V shared memory segments and the like. You must manage these yourself, and it is always messy. Typically, rather than calling exit directly, you're going to invoke a routine that knows about all such resources, frees them, then calls exit. But you still don't need to unwind your stack back to main. Third, the kernel always gets back all of its memory that was granted through conventional means. That's what operating systems do. I think you have a fundamental misunderstanding of what malloc is, and where it lives. Malloc is a user-level routine that lives inside of your process. When you call malloc, it is granting you memory that you already own. Malloc is just a convenience routine that sits between you and the operating system. When you say malloc(4), it does not go off and request 4 bytes from the operating system. It looks into large chunks of memory the operating system granted to it, and gives you some, updating its data structures along the way. If all of its chunks of memory are currently allocated, then it will go ask the operating system for memory - on a Unix machine, typically through a call to brk to mmap. But when it calls brk or mmap, it will request a large amount of memory, say a few megabytes. Then, from that large chunk, it will carve out 4 bytes for you to use. (This is why off-by-one errors are so pernicious in C: the chances are very good that you actually do own that memory, so the kernel will happily allow you to access the value.) Now, even if you are a good citizen and return all memory to malloc, the operating system still has to reclaim tons of memory from you. Which memory? Well, your stacks and such, but also all of that memory that malloc still controls. When you free memory back to malloc, malloc is very unlikely to then give it back to the operating system. So all programs, at exit, will have memory that they own that the kernel will need to reclaim.
- nitrogen 13y agoIf malloc() fails now, it might succeed again later. So you can just go on doing everything else you were doing, then try the memory-hungry operation again in the future. For example, this could be important in systems where you might be controlling physical hardware at the same time as reading commands from a network. It's probably a good idea to maintain control of the hardware even if you don't have enough memory right now to read network commands.
- jzwinck 13y agoIt's an even better idea to make the hardware fail safe, so you can let the program die and not worry too much about it. This does not apply in all cases (cars), but it does apply in many (trains, like a deadman switch for the computer). For a vivid example of why this is an important approach, read about the Therac-25.
- nitrogen 13y agoAbsolutely, but I would still write my software as though the hardware could fail deadly unless doing so made the system less reliable.
- vidarh 13y agoThis is a pet peeve of mine with modern applications: So many of them just throw their metaphorical hands in the air and give up. Prior to swap and excessive abuse of virtual memory this was not an option: If you gave up on running out of memory, your users gave up on your application. On the Amiga, for example, being told an operation failed due to lack of memory and given the chance to correct it by closing down something else was always the expected behaviour. But try having allocations fail today, and watch the mayhem as any number of applications just fall over. So we rely on swap, which leaves systems prone to death spirals when the swap slows things down instead. If embedded systems programmers wrote code the same way modern desktop applications developers did, we'd all be dead.
- kansface 13y agoConversely, if app developers wrote the same code that embedded systems programmers do, we'd never have any apps to use. Its just not worth the trade off- moreover, telling a user to free memory is a losing battle.
- deathanatos 13y agoSane cleanup. Close any open resources, especially interprocess visible resources. Resources entirely in your process, such as memory, will just get freed by the OS; a file might want to be flushed, a database properly closed. Likely, in the frame where you're out of memory, you won't have the context to know what should be done: that is most likely a decision of your caller, or their caller…
- waps 13y agoThis is a brilliant example of why you'd want exceptions. Look at what you're doing for error handling, manually every time. Exceptions do the exact same thing, except: 1) automatically 2) type-safe 3) allow you to give extra detail about the error 4) tools can actually follow the control flow and tell you what's happening and why 5) debuggers can break on all exceptions. Try breaking on "any error" in your code (I do know how C programmers "solve" that : single-stepping. Ugh) In this case, they are better in every way. This is almost as bad as the code in the linux kernel and GNOME where they "don't ever use C++ objects !", and proceed to manually encode virtual method tables. And then you have have 2 object types that "inherit" from eachother (copy the virtual method table) and then proceed to overwrite the wrong indices with the overridden methods (and God forbid you forget to lock down alignment, resulting in having different function pointers overwritten on different architectures). Debugging that will cost you the rest of the week. When it comes to bashing exceptions, it would be better to give people the real reason C++ programmers hate them, it's because of the major unsolvable problem you'll suddenly run in to when using them. In C and C++ you can use exceptions XOR not using exceptions. This sounds like it's not a big deal, until you consider libraries. You want to use old libraries ? No exceptions for you ! (unless you rewrite them) You want to use newer libraries : you don't get to not use exceptions anymore ! You want to combine the two ? That's actually possible but if any exception library interacts with a non-exception library in the call-stack boom. Exceptions are a great idea, but they don't offer a graceful upgrade path. Starting to use exceptions in C++ code is a major rewrite of the code. I guess if you follow the logic of the article that "would be a good thing", but given ... euhm ... reality ... I disagree. Try explaining "I'm adding exceptions to our code, rewriting 3 external libraries in the process" to your boss.
- EpicEng 13y agoThis is a brilliant example of why you'd want exceptions. Look at what you're doing for error handling, manually every time. You say that like safely handling exceptions is trivial. Exceptions are emphatically not "better in every way", they are a mixed bag. They offer many clear benefits (some that you have described here), but at the cost of making your code more difficult to reason about. You essentially end up with a lot of invisible goto's. Problems with exceptions tend to be much more subtle and hard to debug. I'm not against them at all, and often I prefer them, but there are certainly downsides.
- demallien 13y agoIt's not so simple in real life. I use this style of error-handling for only one type of project: a library with unknown users. In that case, as I can't make assumptions about the existence of better error handling systems, it gives the most flexible result. But at a price, I know have to document the error codes, and I had damned well better also provide APIs that allow my client to meaningfully recover from the error. In most I have worked on, this type of error handling is completely inadequate. Think multithreaded applications. The code that needs to handle the error your code just generated isn't in the call stack. This happens very often in my experience, and I have found that the best solution is to post some kind of notification message rather than returning an error code. This creates a dependency on the notification system though, so it's not always the correct solution. The thing that I dislike the most in your example was when you propagated the error from function a out of function b. my most robust code mostly uses void functions. Error codes are only used in cases where the user can actually do some meaningful action in response to the error, and feNkly this is rarely the case. Instead I try as much as possible to correctly handle errors without propagation. It frees up the user of my APIs from having to worry about errors, and in my opinion thus should be a design goal of any API.