2

Suppose I have the following line in my code:

struct info *pinfo = malloc(sizeof(struct info));

Usually there is another line of code like this one:

if (!pinfo)
    <handle this error>

But does it really worth it? especially if the object is so small that the code generated to check it might need more memory than the object itself.

Bite Bytes
  • 1,455
  • 8
  • 24
  • 1
    And over the hill is more green than yesterday - yes, that is nonsense as completely unrelated. Think about it! (Did you find any guarantee small memory blocks are guaranteed to be allocated by `malloc`? And please quantitise "small") – too honest for this site Sep 03 '17 at 12:01
  • [related](https://stackoverflow.com/questions/7947849/can-i-rely-on-malloc-returning-null) – Antti Haapala -- Слава Україні Sep 03 '17 at 12:07
  • 3
    In many large real-world applications 75% or more of the code is there to handle things that should preferably never happen. – Bo Persson Sep 03 '17 at 12:50
  • If you're going to exit the process and have nothing to save / clean-up that the OS won't do... than you might as well crash as handle the error. However, sometimes you would want to notify another process or save existing data to an open file (a recovery file?) before crashing. Some of these can be handled in a signal handler, but mostly error handling is the best option. – Myst Sep 03 '17 at 13:43
  • I don't think this merits an answer, but an important note: your logic around "the code generated to check it might need more memory than the object itself" is invalid except perhaps in extremely controlled microprocessor-programming situations. This space is accounted for in the static size of the program that's known before it runs, and it's possible for the invoking operating system to give you an appropriate error when you try to execute the program if there is not sufficient space to do so. On the other hand... – R.. GitHub STOP HELPING ICE Sep 03 '17 at 15:06
  • ...whether `malloc` succeeds is **not known statically** except in certain very special situations dependent both on your program and its environment. Rather, it depends nontrivially on a lot of mutable state. – R.. GitHub STOP HELPING ICE Sep 03 '17 at 15:09
  • 1
    @Myst: please provide a reference to the standard a C program **is guaranteed** to "crash" when a null pointer is dereferenced. This is not about crash vs. graceful abort. – too honest for this site Sep 03 '17 at 19:56
  • @Olaf , thank you for pointing this out, since there's no guarantee. Dereferencing NULL is undefined behavior. The program might not crash. There's the chance of system or data corruption instead. However, most systems will map the NULL address to a read only page, causing the process to crash due to a segmentation fault. – Myst Sep 03 '17 at 20:04
  • @Myst: Sorry to correct you once more: `NULL` is a macro with a null pointer constant. You mean a _null pointer_, which covers a lot more cases, including the one discussed. Also there is no requirement a null pointer has a bit-representation of all-zero. Don't confuse it with the legacy `0` integer expression! And most systems (by some decades) cannot even check access permissions to memory, because most systems (by units sold as well as different architectures) don't have an MMU, not even an MPU. – too honest for this site Sep 03 '17 at 20:11
  • @Olaf, I guess I should thank you again :) ... I must admit, I'm quite surprised to read that most systems don't have an MMU. I'll have to research that one. I can't imagine writing a kernel without using virtual addressing. It seems to me to be a requirement for process isolation, though I may be wrong. Of course, if we're discussing embedded systems, than I guess it's a whole different story. – Myst Sep 04 '17 at 00:05
  • 1
    @Myst: Typical PC programmer. They always forget the embedded market is much larger than x86, High-End ARM, PPC/POWER and MIPS together. Most of these systems don't even have a full-size OS. FWIW: Each of these computers have a bunch of MCUs already (Keyboard, Mainboard (Power Monitor, PS/2 controller, Mouse, Monitor, SDD/Harddrive, USB-Sticks, SD-Cards, …); these alone outnumber the high-end CPUs, add the MCUs in a typical car (some 10!), white goods, etc. Guess which language is use most for these devices … – too honest for this site Sep 04 '17 at 09:50
  • @Olaf I admit I didn't get to do anything seriously embedded. I guess it was a mistake for me to assume that if `malloc` is asked about, than a complete kernel is involved. I guess that's not always the case. Thanks :) – Myst Sep 04 '17 at 10:02
  • 1
    @Myst: 1) A kernel does not necessarily need process isolation. Surprisingly, in the 60ies-90ies there was a lot of good osftware which did not get daily updates and still did their job very well and was rock-stable. Thjat because it had been thoroughly tested and was designed by people who knew their job very well. They concentrated on writing good software, not implementing fancy features and did not "learn" from youtube videos, though. – too honest for this site Sep 04 '17 at 10:02
  • 1
    @Myst: My comment was not about `malloc` specifically, nor is the question actually. (asking this about `malloc` just shows OP has not understood basic concepts of the language). But as you mrefer to `malloc`: The standard does not mandate to use a specific implementation for it, nor a specific underlying kernel. Typical implementations don't care about virtual/physical memory addresses, even for a full-size OS; they just ask the OS for "pages". Whether these are physical addressed or not is completely irrelevant. Well meant advice: don't mix abstraction layers. – too honest for this site Sep 04 '17 at 10:04

5 Answers5

5

It's true that running out of memory is rare, especially for little test programs that are only allocating tens of bytes of memory, especially on modern systems that have many gigabytes of memory available.

Yet malloc failures are very common, especially for little test programs.

malloc can fail for two reasons:

  1. There's not enough memory to allocate.
  2. malloc detects that the memory-allocation heap is messed up, perhaps because you did something wrong with one of your previous memory allocations.

Now, it turns out that #2 happens all the time.

And, it turns out that #1 is pretty common, too, although not because there's not enough memory to satisfy the allocation the programmer meant to do, but because the programmer accidentally passed a preposterously huge number to malloc, accidentally asking for more memory than there is in the known universe.

So, yes, it turns out that checking for malloc failure is a really good idea, even though it seems like malloc "can't fail".

The other thing to think about is, what if you take the shortcut and don't check for malloc failure? If you sail along and use the null pointer that malloc gave you instead, that'll cause your program to immediately crash, and that'll alert you to your problem just as well as an "out of memory" message would have, without your having to wear your fingers to the bone typing if(!pinfo) and fprintf(stderr, "out of memory\n"), right?

Well, no.

Depending on what your program accidentally does with the null pointer, it's possible it won't crash right away. Anyway, the crash you get, with a message like "Segmentation violation - core dumped" doesn't tell you much, doesn't tell you where your problem is. You can get segmentation violations for all sorts of reasons (especially in little test programs, especially if you're a beginner not quite sure what you're doing). You can spend hours in a futile effort to figure out why your program is crashing, without realizing it's because malloc is returning a null pointer. So, definitely, you should always check for malloc failure, even in the tiniest test programs.

Deciding which errors to test for, versus those that "can't happen" or for whatever reason aren't worth catching, is a hard problem in general. It can take a fair amount of experience to know what is and isn't worth checking for. But, truly, anybody who's programmed in C for very long can tell you emphatically: malloc failure is definitely worth checking for.

If your program is calling malloc all over the place, checking each and every call can be a real nuisance. So a popular strategy is to use a malloc wrapper:

void *my_malloc(size_t n)
{
    void *ret = malloc(n);
    if(ret == NULL) {
        fprintf(stderr, "malloc failed (%s)\n", strerror(errno));
        exit(1);
    }
    return ret;
}

There are three ways of thinking about this function:

  1. Whenever you have some processing that you're doing repetitively, all over the place (in this case, checking for malloc failure), see if you can move it off to (centralize it in) a single function, like this.
  2. Unlike malloc, my_malloc can't fail. It never returns a null pointer. It's almost magic. You can call it whenever and wherever you want, and you never have to check its return value. It lets you pretend that you never have to worry about running out of memory (which was sort of the goal all along).
  3. Like any magical result, my_malloc's benefit — that it never seems to fail — comes at a price. If the underlying malloc fails, my_malloc summarily exits (since it can't return in that case), meaning that the rest of your program doesn't get a chance to clean up. If the program were, say, a text editor, and whenever it had a little error it printed "out of memory" and then basically threw away the file the user had been editing for the last hour, the user might not be too pleased. So you can't use the simple my_malloc trick in production programs that might lose data. But it's a huge convenience for programs that don't have to worry about that sort of thing.
Steve Summit
  • 45,437
  • 7
  • 70
  • 103
  • Playing Devil's advocate a little here -- I have a feeling that malloc() never returns a null in _realistic_ out-of-memory situations in desktop/server Linux. Sure, it will return a null in some cases of developer error -- asking for millions of gigabytes, or whatever -- but I've seen cases where malloc will return a valid value in response to a gigabyte-sized allocation when a system is struggling badly for memory. And this makes me wonder whether, in full-sized Linux systems at least, checking malloc() returns is really worth the effort? I always do, but still... – Kevin Boone Sep 04 '17 at 15:08
3

If malloc fails then chances are the system is out of memory or it's something else your program can't handle. It should abort immediately and at most log some diagnostics. Not handling NULL from malloc will make you end up in undefined behavior land. One might argue that having to abort because of a failure of malloc is already catastrophic but just letting it exhibit UB falls under a worse category.

Hatted Rooster
  • 35,759
  • 6
  • 62
  • 122
1

But what if the malloc fails? You will dereference the NULL pointer, which is UB (undefined behaviour) and your program will (probably) fail!

Sometimes code which checks the correctness of the data is longer than the code which does something with it :).

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
0___________
  • 60,014
  • 4
  • 34
  • 74
  • Note that once upon a time, the world was plagued by code written for DEC VAX where dereferencing the null pointer did not lead to crashes. That lead to one of the "[Ten Commandments](https://www.lysator.liu.se/c/ten-commandments.html)" (Spencer style) — in fact, commandments 2 and 10 apply, but 2 was particularly necessary because of 10. – Jonathan Leffler Sep 03 '17 at 16:31
  • @JonathanLeffler: They seem to be a bit outdated. At least for 3 (did not read all completely) is questionable at the best with parts being definitively discouraged (if you have to cast, you should first think about redesigning; and casting the result of an expression is typically too late already: `void f(long); … f((long)(1 << 31));` is "problematic"). (Update: pos. 4 makes it look like it is older than C99 - I hate websites without an explicit date). – too honest for this site Sep 03 '17 at 20:20
  • @Olaf: yes, they are to some extent a reminder of times past, when the C standard was C90 (and it was still new), and when most systems were 32-bit and there were few 64-bit systems available. And I'm not wholly on board with 1TBS either; I don't use it in my own code (I prefer Allman). But there are still at least elements of truth behind the points made. And, most relevantly to this particular answer, it has not always been true that dereferencing a null pointer causes a crash (but it does cause UB, then and now). – Jonathan Leffler Sep 03 '17 at 20:25
  • @JonathanLeffler: I'd say there are only three commandments: "1) Know thy target. 2) Know thy language. 3) Know thy toolchain." (At a higher level, one could add "4) Understand thy problem"). And to me they seem to be language-agnostic ;-) (It is mostly 1) why I prefer MCUs over the larger Linux-based platforms, and on those it most times does not crash … instantly) – too honest for this site Sep 03 '17 at 20:28
  • @Jonathan Leffler yes - but fail not always means crash. But most probable it will not work as expected – 0___________ Sep 03 '17 at 20:29
0

This is very simply, if you won't check for NULL you might end up with runtime error. Checking for NULL will help you to avoid program from unexpected crash and gracefully handle the error case.

Artem Barger
  • 40,769
  • 9
  • 59
  • 81
0

If you just want to quickly test some algorithm, then fine, but know it can fail. For example run it in the debugger.

When you include it in your Real World Program, then add all the error checking and handling needed.

Paul Ogilvie
  • 25,048
  • 4
  • 23
  • 41
  • 1
    The only problem is that test programs become prototypes which become release programs (under RWP time constraints). And if you don't put the error handling in at the start, you don't add it later — at least, not until after big customers complain and embarrass the company into doing what should have been done in the first place. – Jonathan Leffler Sep 03 '17 at 16:33
  • @JonathanLeffler, that is your discipline as a developer. I prefer to develop and test an algorithm in the pure form. Once done, I document it and add the error checking and handling. – Paul Ogilvie Sep 04 '17 at 10:17