具体来说,强制转换malloc结果有什么危险?


86

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

  1. C FAQ:强制转换malloc的返回值怎么了?
  2. SO:我应该显式转换malloc()的返回值吗?
  3. SO:C中不必要的指针广播
  4. SO:我是否强制转换malloc的结果?

C FAQ和上述问题的许多答案都引用了一个神秘的错误,该错误malloc会隐藏cast的返回值。但是,它们都没有在实践中给出此类错误的具体示例。现在注意我说的是错误,不是警告

现在给出以下代码:

#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

那么,谁能给出一个具体的代码示例,说明由于castmalloc的返回值而可能发生的编译或运行时错误,或者这仅仅是城市传说?

编辑我遇到了两个有关此问题的书面论据:

  1. 支持强制转换:CERT咨询:立即将内存分配函数调用的结果强制转换为指向已分配类型的指针
  2. 反对投放广告 (自2012年2月14日起出现404错误:使用2010年1月27日的Internet存档Wayback Machine副本。{2016-03-18:“由于robots.txt,页面无法被抓取或显示。”})

6
强制转换void指针允许将代码编译为C ++;有人说这是一个功能,我会说这是一个错误;)
Christoph

1
另外,请阅读您的第一个链接的注释,因为它描述了您应该执行的工作而不是强制转换:securecoding.cert.org/confluence/display/seccode/…–
Christoph

3
我将采纳CERT的建议,将演员表包括在内。另外,我永远不会忘记包含stdlib.h。:)
Abhinav 2012年

1
这是一个由于强制转换malloc的返回值而导致的编译运行时错误的SO示例int*在64位arch上强制转换为。
John_West

1
这个问题是标签C没有C++(他们是两种不同的语言),因此,任何的讨论(如在一些问题的答案),不相关的这个问题。
user3629249

Answers:


66

你不会得到 编译器错误,但是编译器警告。正如您引用的消息来源(尤其是第一个消息)所说,您可以使用强制转换如果不包含stdlib.h会遇到不可预知的运行时错误

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

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

排除强制转换以防止出现此错误与编写代码的原因大致相同

if (0 == my_var)

代替

if (my_var == 0)

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

强制转换由malloc以下方法返回的值也是如此:我更喜欢在编程中进行显式表示,并且通常我会仔细检查以包括我使用的所有函数的头文件。


2
似乎因为编译器警告不兼容的隐式声明,所以只要您注意编译器警告,这就不是问题。
罗伯特·S·巴恩斯

4
@Robert:是的,考虑到有关编译器的某些假设。当人们给出一般如何最好地编写C的建议时,他们不能假定收到建议的人正在使用最新版本的gcc。
史蒂夫·杰索普

4
哦,第二个问题的答案是,调用方包含用于拾取返回值(它认为是int)并将其转换为T *的代码。被调用方只写返回值(作为void *)并返回。因此,根据调用约定:int return和void * return可能在或可能不在“相同位置”(寄存器或堆栈插槽)中;int和void *的大小可以相同或可以不相同;两者之间的转换可能是空操作,也可能不是。因此它可能“正常工作”,或者值可能已损坏(也许丢失了一些位),或者调用者可能完全选择了错误的值。
史蒂夫·杰索普

1
@ RobertS.Barnes晚了,但是:返回值通常不是函数签名的一部分,即使在C ++中也是如此。链接器仅会生成一个跳到符号的过程,仅此而已。
彼得-恢复莫妮卡

3
如果不使用stdlib.h,则使用强制类型转换时可能会遇到无法预料的运行时错误。没错,但是stdlib.h即使您仅收到“隐式声明”警告,也不包括本身已经存在的错误。
Jabberwocky

45

反对强制转换结果的高级观点之一 malloc尽管我认为它比众所周知的低级问题(例如在缺少声明的情况下将指针截断)更重要,但通常不会提及。

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

类型名称属于声明。类型名称应尽可能仅限于声明,并且仅限于声明。

从这个角度来看,这段代码是不好的

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

这更好

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

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


首先,我认为这大致上是相同的原因:stackoverflow.com/questions/953112/…,但重点是类型独立性而不是DIY。当然,第一个跟在第二个之后(反之亦然),因此至少有时会提到它。:)
放松

5
@unwind,您最有可能是说而不是DIY
kratenko 2012年

18

假定非原型函数返回int

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

另外,当然,有些人认为警告错误的,即代码应该在没有编译。

就我个人而言,我认为不需要强制转换void *为其他指针类型的事实是C的一项功能,因此请考虑确实要破坏的代码。


14
我相信编译器比我更了解该语言,因此如果它警告我某些事情,我会引起注意。
捷尔吉Andrasek

3
在许多项目中,C代码被编译为C ++,你就需要转换void*
laalto

nit:“默认情况下,假定非原型函数返回int。” -您的意思是可以更改非原型函数的返回类型吗?
09年

1
@laalto-是的,但不应该这样。C是C,不是C ++,应使用C编译器而不是C ++编译器进行编译。没有任何借口:GCC(目前最好的C编译器之一)在几乎所有可以想象的平台上运行(并且也生成高度优化的代码)。除了懒惰和宽松的标准之外,您可能还需要使用C ++编译器编译C的原因是什么?
克里斯·卢兹

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,但是很难理解在这两个函数中都可以使用的标头。
史蒂夫·杰索普

11

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

编辑:太简短了,很抱歉。这是出于讨论目的的示例代码片段。

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

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

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


1
您能详细解释这种情况如何发生吗?
罗伯特·S·巴恩斯

5
我认为Peeter Joot被搞清楚的是“默认情况下,非功能原型假定返回INT” W / O,包括stdlib.h中,和sizeof(int)的是32个比特,而的sizeof(PTR)是64
试验

4

可重用的软件规则:

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


希望(最近)在gcc中包含链接时优化功能(请参阅gcc.gnu.org/ml/gcc/2009-10/msg00060.html),不再需要在头文件中声明内联函数
Christoph

你有坏主意。您是否知道不同的编译器/版本/体系结构之间的可移植性和跨平台性?好吧,你可能不会。那么可重用是什么意思?
测试

2
在编写C ++时,malloc / free不是正确的方法。而是使用new / delete。IE浏览器在C ++代码中应该没有/对
nalloc

3
@ user3629249:当写这需要从内可使用的功能或者C代码或C ++代码,用malloc/free两个易于优于试图使用malloc在C和newC ++中,特别是如果数据结构C和C之间共享++代码,并且有可能用C代码创建对象并用C ++代码发布对象,反之亦然。
超级猫

3

可以将C中的void指针分配给任何指针,而无需显式强制转换。编译器会给出警告,但可以通过将类型强制转换为相应的类型在C ++中重用malloc()。由于C不是严格的类型检查,因此也可以使用类型转换在C中使用。但是C ++严格地进行类型检查,因此需要在C ++中进行类型转换。malloc()


如果您在C ++中使用malloc,则最好有一个很好的理由!; p
antant
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.