1

I'm trying to port an application from Linux to Mac Os X (leopard), but when I execute it, I have this error message : malloc: *** error for object 0x100160 : double free.

I've reproduced this issue with the code below :

//main.cpp 
#include <stdio.h>
#include <wchar.h>

int main(int argc, char*argv[])
{
    wchar_t *b=NULL;
    printf("a=%ls, b=%ls \n", L"a", b);
}

Compiled with gcc:

gcc main.cpp -o test

The output of the execution:

a=a, b=(null)
test (5337) malloc: *** error for object 0x100160 : double free
*** set a breakpoint in malloc_error_break to debug

It's strange because if I use this line : printf("a=%ls, b=%ls", b, b), no error are printed. Furthermore, I can't use wprintf(L"a=%ls, b=%ls", a, b). On Fedora 13, this program don't print any error.

Is it a printf bug? How can I remove this error?

Fred Foo
  • 355,277
  • 75
  • 744
  • 836
Vincent
  • 1,013
  • 14
  • 33
  • 3
    Wait, you're asking why you run into problems when you pass a NULL pointer into a function that generally expects it to point to data? – Cascabel Jan 18 '11 at 16:55
  • I can't get any explanation of this behavior except a bug in CRT library. – Al Kepp Jan 18 '11 at 16:56
  • 3
    @Al: Are you suggesting that there's some guarantee printf will gracefully handle null pointers? I think the explanation you're searching for is "undefined behavior is undefined." – Cascabel Jan 18 '11 at 16:59
  • @Jefromi: As I stated in my comment below, I think that printf should choose a different way of refusing invalid input than corrupting or destroying memory heap. Just my personal opinion. – Al Kepp Jan 18 '11 at 17:09

4 Answers4

6

You cannot print NULL pointers as strings, that's undefined behavior. From the C99 standard, §7.19.6.1/8

The conversion specifiers and their meanings are:
...
s     If no l length modifier is present, the argument shall be a pointer to the initial element of an array of character type. ...

If an l length modifier is present, the argument shall be a pointer to the initial element of an array of wchar_t type.

Since NULL pointers are not explicitly allowed, they are disallowed. You should change your code to something like:

printf("a=%ls, b=%ls \n", L"a", b ? b : L"(null)");
Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
  • printf is allowed to refuse NULL. But please show me where is it written that printf is allowed to corrupt memory heap. – Al Kepp Jan 18 '11 at 17:07
  • 4
    @Al Kepp undefined behavior is allowed to do anything. Including "appears to work", crash, corrupt memory, set your printer on fire. Other function, e.g. strlen,strcpy, usually just causes a segfault if you use them in an undefined manner. printf happens to corrupt memory – nos Jan 18 '11 at 17:09
  • 2
    When the code causes undefined behavior, all bets are off - nobody is going to list all the effects invalid code may cause on all the different platforms. Corrupting stack or heap is really only probable. – eq- Jan 18 '11 at 17:10
5

This is simply a bug in vprintf_l, which handles printf (and presumably all its friends).

Strictly speaking, the library is within its rights to handle this situation by doing whatever it likes, heap corruption included, but judging by the code the intent -- as should be the case for any non-rubbish printf implementation -- is to handle NULL strings sensibly. There is simply a bug in the code that does this. These things happen.

If an attempt is made to print a NULL wide char pointer after a previous non-NULL wide char pointer was printed, vprintf_l will cock up on the next string argument, or when vprintf_l exits. (There may be other ways to get this to happen, too, I suppose - I didn't check.)

The offending code is here:

    case 's':
        if (flags & LONGINT) {
            wchar_t *wcp;

            if (convbuf != NULL)
                free(convbuf);
            if ((wcp = GETARG(wchar_t *)) == NULL)
                cp = "(null)";
            else {
                convbuf = __wcsconv(wcp, prec, loc);
                if (convbuf == NULL) {
                    fp->_flags |= __SERR;
                    goto error;
                }
                cp = convbuf;
            }
        } else if ((cp = GETARG(char *)) == NULL)

If GETARG(wchar_t *) returns NULL, convbuf will point to the old (and now freed) buffer. Then, when the function exits, there is a double free:

if (convbuf != NULL)
    free(convbuf);

The same would apply were there another string argument, though in this case the double free happens in the case 's' code above:

printf("a=%ls, b=%ls c=%ls\n", L"a", b, L"c");

The solution of course would be to set convbuf to NULL after it is freed.

The printf code is here:

http://www.opensource.apple.com/source/Libc/Libc-594.1.4/stdio/vfprintf-fbsd.c

Judging by the disassembly, it is the code that is used for the default runtime in Snow Leopard.

  • Great answer. Another solution would be to move the convbuf check to inside the else -- no need to free it unless you're going to re-use it. – tomlogic Jan 18 '11 at 23:45
1

On Ubuntu it prints a=a, b=(null), but in general it is not a good idea to try to print NULL strings.

Elalfer
  • 5,312
  • 20
  • 25
-1

Although it is not a good practice to pass a NULL pointer to printf, from generic point of view I cannot accept a situation when any function (including printf) reacts to an invalid input by destroying memory heap. So although you should not pass a NULL there I consider this behavior as a bug in library. If a function gets invalid arguments it can scream a lot but is not supposed to destroy memory heap.

Of course sometimes it is not possible to know if a parameter is valid, but here we have a NULL constant and it is simple to know that it cannot be dereferenced or something.

Al Kepp
  • 5,831
  • 2
  • 28
  • 48
  • It's certainly not very polite to first print `b=(null)` and then eat flaming death... – Fred Foo Jan 18 '11 at 17:01
  • While it's true that it would be better if `printf` reacted better to invalid input, it's not a bug because the C standard does not require it to do so. – Adam Rosenfield Jan 18 '11 at 17:04
  • @Adam: So does C standard somewhere declare that it is 100% legal to corrupt memory stack when you get NULL argument where it shouldn't be? I think that C standard refuses to call free twice, so printf violates it here. Maybe I take it wrong and this strange behavior is legal, but I call it "avoidable accident". – Al Kepp Jan 18 '11 at 17:11
  • 2
    @Al: It's totally legal. Whether it's *good* is a different question; as Adam says, it probably would be nice if printf behaved more kindly. But as far as the standard's concern, this is all good. (And in fact, I'm pretty sure the standard says that the result of a double free is undefined too, which is not "refusing to call it twice".) See for example [this question](http://stackoverflow.com/questions/2397984/undefined-unspecified-and-implementation-defined-behavior). (That's really C++, but I couldn't find just C, and it's similar.) – Cascabel Jan 18 '11 at 17:25