【问题标题】:Specifically, what's dangerous about casting the result of malloc?具体来说,强制转换 malloc 的结果有什么危险?
【发布时间】:2010-12-06 15:13:57
【问题描述】:

现在,在人们开始将此标记为 dup 之前,我已经阅读了以下所有内容,但都没有提供我正在寻找的答案:

  1. C FAQ: What's wrong with casting malloc's return value?
  2. SO: Should I explicitly cast malloc()’s return value?
  3. SO: Needless pointer-casts in C
  4. SO: Do I cast the result of malloc?

C FAQ 和上述问题的许多答案都引用了一个神秘的错误,即强制转换malloc 的返回值可以隐藏;但是,它们都没有给出实践中这种错误的具体例子。现在注意我说的是error,而不是warning

现在给出以下代码:

#include <string.h>
#include <stdio.h>
// #include <stdlib.h>

int main(int argc, char** argv) {

    char * p = /*(char*)*/malloc(10);
    strcpy(p, "hello");
    printf("%s\n", p);

    return 0;
}

使用 gcc 4.2 编译上述代码,使用和不使用强制转换都会给出相同的警告,并且程序可以正确执行并在两种情况下提供相同的结果。

anon@anon:~/$ gcc -Wextra nostdlib_malloc.c -o nostdlib_malloc
nostdlib_malloc.c: In function ‘main’:
nostdlib_malloc.c:7: warning: incompatible implicit declaration of built-in function ‘malloc’
anon@anon:~/$ ./nostdlib_malloc 
hello

那么任何人都可以给出一个具体的代码示例,说明由于转换 malloc 的返回值而可能发生的编译或运行时错误,或者这只是一个都市传说?

编辑关于这个问题,我遇到了两个写得很好的论点:

  1. 支持选角:CERT 咨询:Immediately cast the result of a memory allocation function call into a pointer to the allocated type
  2. Against Casting(2012 年 2 月 14 日出现 404 错误:使用 2010 年 1 月 27 日的 Internet Archive Wayback Machine 副本。{2016 年 3 月 18 日:“由于 robots.txt,无法抓取或显示页面。”})

【问题讨论】:

  • 铸造void指针允许将代码编译为C++;有人说这是一个功能,我会说这是一个错误;)
  • 另外,请阅读第一个链接的 cmets,因为它描述了您应该做什么而不是强制转换:securecoding.cert.org/confluence/display/seccode/…
  • 我会接受 CERT 的建议,包括演员。另外,我永远不会忘记包含 stdlib.h。 :)
  • Here is a SO-example 编译运行时错误,因为在 64 位架构上转换为 malloc 的返回值:转换为 int*
  • 这个问题被标记为C 而不是C++(它们是两种不同的语言)所以任何讨论(如某些答案)都与这个问题无关。

标签: c


【解决方案1】:

您不会收到编译器错误,而是编译器警告。正如您引用的消息来源所说(尤其是first one),您可以在使用演员阵容时遇到不可预知的运行时错误不包括stdlib.h.

所以你这边的错误不是演员表,而是忘记包含stdlib.h。编译器可能假设malloc 是一个返回int 的函数,因此将malloc 实际返回的void* 指针转换为int,然后由于显式转换而转换为您的指针类型。在某些平台上,int 和指针可能占用不同的字节数,因此类型转换可能会导致数据损坏。

幸运的是,现代编译器会发出警告,指出您的实际错误。请参阅您提供的 gcc 输出:它警告您 implicit 声明 (int malloc(int)) 与内置 malloc 不兼容。所以gcc 似乎知道malloc,即使没有stdlib.h

省略演员表以防止此错误与写作的推理基本相同

if (0 == my_var)

而不是

if (my_var == 0)

因为如果混淆===,后者可能会导致严重的错误,而第一个会导致编译错误。我个人更喜欢后一种风格,因为它更能反映我的意图,而且我不会犯这种错误。

转换malloc返回的值也是如此:我更喜欢在编程中明确表示,我通常会仔细检查以包含我使用的所有函数的头文件。

【讨论】:

  • 看来既然编译器会警告不兼容的隐式声明,那么只要您注意编译器警告,这不是问题。
  • @Robert:是的,考虑到编译器的某些假设。当人们就如何最好地编写 C一般提出建议时,他们不能假设收到建议的人使用的是最新版本的 gcc。
  • 哦,第二个问题的答案是调用者包含获取返回值(它认为是 int)并将其转换为 T* 的代码。被调用者只是写入返回值(作为 void*)并返回。所以取决于调用约定: int 返回和 void* 返回可能在也可能不在“同一个地方”(寄存器或堆栈槽); int 和 void* 可能大小相同,也可能不同;两者之间的转换可能是也可能不是无操作。因此它可能“正常工作”,或者该值可能已损坏(可能丢失了一些位),或者调用者可能会获取完全错误的值。
  • @RobertS.Barnes 迟到了,但是:返回值通常不是函数签名的一部分,即使在 C++ 中也是如此。链接器只是生成一个符号跳转,仅此而已。
  • 在不包含 stdlib.h 的情况下使用强制转换时会出现不可预知的运行时错误。没错,但不包括 stdlib.h 本身已经是一个错误,即使您只收到“隐式声明”警告。
【解决方案2】:

反对强制转换malloc 的结果的一个很好的高级论点通常没有被提及,尽管在我看来,它比众所周知的低级问题更重要(比如在声明丢失)。

一个好的编程习惯是编写尽可能与类型无关的代码。这尤其意味着,代码中应该尽可能少地提及类型名称,或者最好根本不提及。这适用于类型转换(避免不必要的类型转换)、作为sizeof 参数的类型(避免在sizeof 中使用类型名称)以及通常对类型名称的所有其他引用。

类型名称属于声明。尽可能将类型名称限制在声明中,并且仅限于声明。

从这个角度来看,这段代码很糟糕

int *p;
...
p = (int*) malloc(n * sizeof(int));

这好多了

int *p;
...
p = malloc(n * sizeof *p);

不仅仅是因为它“不转换 malloc 的结果”,而是因为它与类型无关(或类型不可知,如果您愿意),因为它会自动将自身调整为任何类型 p声明为,无需用户的任何干预。

【讨论】:

  • Fwiw,我认为这或多或少与以下原因相同:stackoverflow.com/questions/953112/… 但专注于类型独立性而不是 DIY。当然,第一个是从第二个(反之亦然)开始的,所以至少有时会提到它。 :)
  • @unwind 你的意思很可能是 DRY 而不是 DIY
【解决方案3】:

假定非原型函数返回int

因此,您将 int 转换为指针。如果您的平台上的指针比ints 宽,这是非常危险的行为。

当然,还有一些人认为警告错误,即代码应该在没有它们的情况下编译。

就个人而言,我认为您不需要将void * 强制转换为另一种指针类型这一事实是 C 中的一个特性,并且考虑到确实会被破坏的代码。

【讨论】:

  • 我相信编译器比我更了解该语言,所以如果它警告我某事,我会注意。
  • 在许多项目中,C 代码编译为 C++,您确实需要转换 void*
  • nit: "默认情况下,假定非原型函数返回int。" -- 你的意思是可以改变非原型函数的返回类型吗?
  • @laalto - 是,但不应该是。 C 是 C,而不是 C++,应该使用 C 编译器而不是 C++ 编译器进行编译。没有任何借口:GCC(目前最好的 C 编译器之一)几乎可以在所有可以想象的平台上运行(并且还会生成高度优化的代码)。除了懒惰和松散的标准之外,你可能有什么理由用 C++ 编译器编译 C?
  • 您可能希望同时编译为 C 和 C++ 的代码示例:#ifdef __cplusplus \nextern "C" { \n#endif static inline uint16_t swb(uint16_t a) {return ((a &lt;&lt; 8) | ((a &gt;&gt; 8) &amp; 0xFF); } \n#ifdef __cplusplus\n } \n#endif。现在,我真的不知道为什么要在静态内联函数中调用 malloc,但是在两者中都可以使用的标头几乎是闻所未闻的。
【解决方案4】:

如果在 64 位模式下编译时这样做,返回的指针将被截断为 32 位。

编辑: 抱歉太简短了。这是一个用于讨论的示例代码片段。

主要的() { char * c = (char *)malloc(2) ; printf("%p", c) ; }

假设返回的堆指针大于 int 中可表示的值,例如 0xAB00000000。

如果 malloc 的原型没有返回指针,则返回的 int 值最初将在某个寄存器中,所有有效位都已设置。现在编译器说,“好的,我如何将和 int 转换为指针”。这将是低阶 32 位的符号扩展或零扩展,它被告知 malloc 通过省略原型“返回”。由于 int 已签名,我认为转换将是符号扩展,在这种情况下会将值转换为零。返回值为 0xABF0000000 时,您将获得一个非零指针,当您尝试取消引用它时也会带来一些乐趣。

【讨论】:

  • 你能详细解释一下这是怎么发生的吗?
  • 我认为 Peeter Joot 正在弄清楚“默认情况下,假定非原型函数返回 int”w/o 包括 stdlib.h,并且 sizeof(int) 是 32 位,而 sizeof(ptr ) 是 64。
【解决方案5】:

可重用的软件规则:

在编写使用 malloc() 的内联函数的情况下,为了使其也可用于 C++ 代码,请进行显式类型转换(例如 (char*));否则编译器会报错。

【讨论】:

  • 希望,随着(最近)在 gcc 中包含链接时优化(参见 gcc.gnu.org/ml/gcc/2009-10/msg00060.html ),将不再需要在头文件中声明内联函数
  • 你有坏主意。您是否知道不同编译器/版本/架构之间的可移植性和跨平台性?好吧,你可能不会。那么可重用是什么意思呢?
  • 在编写 C++ 时,malloc/free 不是正确的方法。而是使用新/删除。 IE。在 C++ 代码中应该没有/nada/zero 调用 malloc/free
  • @user3629249:当编写需要在 C 代码或 C++ 代码中使用的函数时,两者都使用malloc/free 很容易比尝试在 C 中使用 malloc 和在 C++ 中使用 new 更好,特别是如果数据结构在 C 和 C++ 代码之间共享,并且有可能在 C 代码中创建对象并在 C++ 代码中释放对象,反之亦然。
【解决方案6】:

可以将 C 中的 void 指针分配给任何指针,而无需显式强制转换。编译器会给出警告,但它可以通过将malloc() 类型转换为相应的类型来在 C++ 中重用。如果没有类型转换,它也可以在 C 中使用,因为 C 没有严格的类型检查。但是C++ 是严格的类型检查,所以需要在 C++ 中键入 cast malloc()

【讨论】:

  • 如果你在 C++ 中使用 malloc,你最好有一个该死的好理由! ;p
猜你喜欢
  • 2012-11-18
  • 2015-12-15
  • 2017-07-25
  • 2012-05-21
  • 1970-01-01
  • 2012-08-30
相关资源
最近更新 更多