具体来说,铸造malloc的结果有什么危险?

Rob*_*nes 85 c

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

  1. C FAQ:转换malloc的返回值有什么问题?
  2. SO:我应该显式地转换malloc()的返回值吗?
  3. SO:在C中不必要的指针转换
  4. SO:我是否施放了malloc的结果?

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

现在给出以下代码:

#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;
}
Run Code Online (Sandbox Code Playgroud)

用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
Run Code Online (Sandbox Code Playgroud)

那么,任何人都可以提供一个特定的代码示例,说明由于转换malloc返回值而可能发生的编译或运行时错误,或者这仅仅是一个城市传奇?

编辑我在这个问题上遇到了两个写得很好的论点:

  1. 利于强制转换:CERT建议:立即将内存分配函数调用的结果转换为指向已分配类型的指针
  2. 反对强制转换 (截至2012-02-14的404错误:使用2010-01-27中的互联网归档Wayback Machine副本.{2016-03-18:"由于robots.txt而无法抓取或显示页面."})

Fer*_*yer 66

您不会收到编译器错误,但会收到编译器警告.作为您引用的来源(特别是第一个),在使用强制转换而不包括时,您可能会遇到不可预测的运行时错误.stdlib.h

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

幸运的是,现代编译器会发出警告,指出您的实际错误.查看gcc您提供的输出:它警告您隐式声明(int malloc(int))与内置不兼容malloc.所以gcc似乎malloc甚至没有知道stdlib.h.

抛出演员以防止这种错误与写作大致相同

if (0 == my_var)
Run Code Online (Sandbox Code Playgroud)

代替

if (my_var == 0)
Run Code Online (Sandbox Code Playgroud)

因为后者可能会导致一个严重的错误,如果一个会混淆===,而第一个将导致编译错误.我个人更喜欢后者的风格,因为它更好地反映了我的意图,我不倾向于犯这个错误.

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

  • @Robert:是的,给出了关于编译器的某些假设.当人们提供关于如何最好地编写C*的建议*时,他们不能假设接收建议的人正在使用最新版本的gcc. (4认同)
  • 哦,第二个问题的答案是调用者包含获取返回值的代码(它认为是int),并将其转换为T*.被调用者只是写回返值(作为void*)并返回.所以取决于调用约定:int returns和void*返回可能也可能不在"相同位置"(寄存器或堆栈槽); int和void*可能是也可能不是相同的大小; 两者之间的转换可能是也可能不是无操作.所以它可能"正常工作",或者值可能被破坏(可能会丢失一些位),或者调用者可能完全接收到错误的值. (4认同)
  • _在使用强制转换而不包含stdlib.h_时,您可能会遇到不可预测的运行时错误.这是真的,但不包括`stdlib.h`本身已经是一个错误,即使你只得到"隐含声明"警告. (3认同)
  • 似乎因为编译器警告不兼容的隐式声明,所以只要您注意编译器警告,这就不是问题。 (2认同)

AnT*_*AnT 45

malloc虽然在我看来,它比知名的低级问题(比如在缺少声明时截断指针)更重要,但是反对强制转换结果的一个好的高级别论据往往没有被提及.

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

类型名称属于声明.类型名称应尽可能限于声明,仅限于声明.

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

int *p;
...
p = (int*) malloc(n * sizeof(int));
Run Code Online (Sandbox Code Playgroud)

这要好得多

int *p;
...
p = malloc(n * sizeof *p);
Run Code Online (Sandbox Code Playgroud)

不仅仅是因为它"不会导致结果malloc",而是因为它是独立于类型的(或者类型无关,如果你愿意的话),因为它会自动调整自身适用于p声明的任何类型,而不需要任何干预用户.

  • @unwind你很可能意味着**干**而不是**DIY** (5认同)

unw*_*ind 18

假设非原型函数返回int.

所以你要投射int指针.如果指针比int平台上的指针宽,那么这是一种高风险的行为.

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

就个人而言,我认为你不需要转换void *为另一种指针类型的事实是C中的一个特性,并考虑要破坏的代码.

  • 我有这样的信念,编译器比我更了解语言,所以如果它警告我某事,我会注意. (14认同)
  • 在许多项目中,C代码被编译为C++,你需要转换`void*`. (3认同)
  • 您可能希望编译为C和C++的代码示例:`#ifdef __cplusplus \nextern"C"{\n#endif static inline uint16_t swb(uint16_t a){return((a << 8)|((a >> 8)&0xFF);} \n #ifdef __cplusplus \n} \n#endif`.现在,为什么你想在静态内联函数中调用malloc我真的不知道,但是在哪个头文件中工作两者都闻所未闻. (3认同)

Pee*_*oot 11

如果在64位模式下编译时执行此操作,则返回的指针将被截断为32位.

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

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

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

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

  • 我认为Peeter Joot正在弄清楚"默认情况下,假定非原型函数返回int",包括stdlib.h,而sizeof(int)是32位,而sizeof(ptr)是64位. (5认同)

Tes*_*est 5

可重用软件规则:

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

  • @user3629249:当编写需要在*C代码或C++代码中使用的函数时,对两者使用“malloc”/“free”往往比尝试在C和“new”中使用“malloc”更好` 在 C++ 中,特别是当数据结构在 C 和 C++ 代码之间共享时,并且对象有可能在 C 代码中创建并在 C++ 代码中释放,反之亦然。 (4认同)
  • 在编写 C++ 时,malloc/free 不是正确的方法。而是使用 new/delete。IE 在 C++ 代码中应该没有/nada/零调用 malloc/free (2认同)