【问题标题】:malloc : error double free with printf and a NULL wchar_t*malloc:使用 printf 和 NULL wchar_t* 释放错误
【发布时间】:2011-01-18 16:48:06
【问题描述】:

我正在尝试将应用程序从 Linux 移植到 Mac Os X (leopard),但是当我执行它时,我收到以下错误消息:malloc: *** error for object 0x100160 : double free

我已经用下面的代码重现了这个问题:

//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);
}

用 gcc 编译:

gcc main.cpp -o test

执行的输出:

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

这很奇怪,因为如果我使用这一行:printf("a=%ls, b=%ls", b, b),则不会打印任何错误。 此外,我不能使用wprintf(L"a=%ls, b=%ls", a, b)。 在 Fedora 13 上,此程序不会打印任何错误。

这是一个 printf 错误吗? 如何消除此错误?

【问题讨论】:

  • 等等,你问为什么在将 NULL 指针传递给通常期望它指向数据的函数时会遇到问题?
  • 除了 CRT 库中的错误之外,我无法得到有关此行为的任何解释。
  • @Al:您是否建议 printf 可以优雅地处理空指针?我认为您正在寻找的解释是“未定义的行为是未定义的。”
  • @Jefromi:正如我在下面的评论中所说,我认为 printf 应该选择一种不同的方式来拒绝无效输入,而不是破坏或破坏内存堆。只是我个人的看法。

标签: c macos gcc printf


【解决方案1】:

您不能将 NULL 指针打印为字符串,这是未定义的行为。来自 C99 标准,§7.19.6.1/8

转换说明符及其含义是:
...
s     如果不存在 l 长度修饰符,则参数应为指向字符类型数组的初始元素的指针。 ...

如果存在l 长度修饰符,则参数应为指向初始值的指针 wchar_t 类型的数组的元素。

由于没有明确允许 NULL 指针,因此它们是不允许的。您应该将代码更改为:

printf("a=%ls, b=%ls \n", L"a", b ? b : L"(null)");

【讨论】:

  • printf 被允许拒绝 NULL。但是请告诉我允许 printf 破坏内存堆的地方是哪里写的。
  • @Al Kepp 未定义的行为被允许做任何事情。包括“似乎可以工作”、崩溃、内存损坏、打印机着火。其他功能,例如strlen,strcpy,如果您以未定义的方式使用它们,通常只会导致段错误。 printf 碰巧损坏了内存
  • 当代码导致未定义的行为时,所有的赌注都没有了——没有人会列出无效代码在所有不同平台上可能导致的所有影响。损坏堆栈或堆实际上只是可能的。
【解决方案2】:

这只是vprintf_l 中的一个错误,它处理printf(可能还有它的所有朋友)。

严格来说,图书馆有权通过做任何喜欢的事情来处理这种情况,包括堆损坏,但从代码判断意图 - 任何非垃圾 printf 实现的情况应该如此 - - 是明智地处理NULL 字符串。执行此操作的代码中只有一个错误。这些事情都会发生。

如果在打印了之前的非NULL 宽字符指针之后尝试打印NULL 宽字符指针,vprintf_l 将在下一个字符串参数或vprintf_l 退出时停止。 (我想可能还有其他方法可以实现这一点 - 我没有检查。)

有问题的代码在这里:

    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)

如果GETARG(wchar_t *) 返回NULLconvbuf 将指向旧的(现在已释放)缓冲区。然后,当函数退出时,有一个double free:

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

如果有另一个字符串参数,同样适用,但在这种情况下,双重释放发生在上面的 case 's' 代码中:

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

解决方案当然是在convbuf被释放后设置为NULL

printf 代码在这里:

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

从反汇编来看,是雪豹默认运行时使用的代码。

【讨论】:

  • 很好的答案。另一种解决方案是将 convbuf 检查移到 else 内部——除非您要重新使用它,否则无需释放它。
  • 谢谢!这对我帮助很大!
【解决方案3】:

在 Ubuntu 上它会打印 a=a, b=(null),但通常尝试打印 NULL 字符串并不是一个好主意。

【讨论】:

    【解决方案4】:

    虽然将 NULL 指针传递给 printf 不是一个好习惯,但从一般的角度来看,我不能接受任何函数(包括 printf)通过破坏内存堆来对无效输入做出反应的情况。因此,尽管您不应该在那里传递 NULL,但我认为这种行为是库中的错误。如果一个函数得到无效的参数,它可能会尖叫很多,但不应该破坏内存堆。

    当然有时无法知道参数是否有效,但这里我们有一个 NULL 常量,很容易知道它不能被取消引用或其他什么。

    【讨论】:

    • 先打印b=(null)再吃火死当然不太礼貌……
    • 虽然printf 对无效输入做出更好的反应确实会更好,但这不是错误,因为 C 标准不要求它这样做。
    • @Adam:那么 C 标准是否在某处声明当您在不应该出现 NULL 参数时破坏内存堆栈是 100% 合法的?我认为 C 标准拒绝调用 free 两次,所以 printf 在这里违反了它。也许我理解错了,这种奇怪的行为是合法的,但我称之为“可避免的事故”。
    • @Al:这是完全合法的。它是否是另一个问题;正如亚当所说,如果 printf 表现得更友善,那可能会很好。但就标准而言,这一切都很好。 (事实上​​,我很确定标准说双重释放的结果也是未定义的,这不是“拒绝调用它两次”。)例如参见this question。 (这确实是 C++,但我找不到只有 C,而且很相似。)
    猜你喜欢
    • 1970-01-01
    • 2020-08-08
    • 2018-06-13
    • 1970-01-01
    • 2010-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多