具体来说,抛出 malloc 的结果有什么危险?

现在,在人们开始评论这个问题之前,我已经阅读了以下所有内容,但没有一个能给出我想要的答案:

  1. C 常见问题解答: 转换 malloc 的返回值有什么问题吗?
  2. SO: 是否应该显式强制转换 malloc ()的返回值?
  3. SO: C 中不必要的指针强制转换
  4. 那么: 我要强制执行 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;
}

用 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 建议: 立即将内存分配函数调用的结果强制转换为指向所分配类型的指针
  2. 反对转换 (404错误,截至2012-02-14: 使用2010-01-27的 网站时光机副本。{2016-03-18: “由于 robots.txt,无法抓取或显示页面。”})
18869 次浏览

Non-prototyped functions are assumed to return int.

So you're casting an int to a pointer. If pointers are wider than ints on your platform, this is highly risky behavior.

Plus, of course, that some people consider warnings to be errors, i.e. code should compile without them.

Personally, I think the fact that you don't need to cast void * to another pointer type is a feature in C, and consider code that does to be broken.

You won't get a compiler error, but a compiler warning. As the sources you cite say (especially the first one), you can get an unpredictable runtime error when using the cast without including stdlib.h.

So the error on your side is not the cast, but forgetting to include stdlib.h. Compilers may assume that malloc is a function returning int, therefore converting the void* pointer actually returned by malloc to int and then to your pointer type due to the explicit cast. On some platforms, int and pointers may take up different numbers of bytes, so the type conversions may lead to data corruption.

Fortunately, modern compilers give warnings that point to your actual error. See the gcc output you supplied: It warns you that the implicit declaration (int malloc(int)) is incompatible to the built-in malloc. So gcc seems to know malloc even without stdlib.h.

Leaving out the cast to prevent this error is mostly the same reasoning as writing

if (0 == my_var)

instead of

if (my_var == 0)

since the latter could lead to a serious bug if one would confuse = and ==, whereas the first one would lead to a compile error. I personally prefer the latter style since it better reflects my intention and I don't tend to do this mistake.

The same is true for casting the value returned by malloc: I prefer being explicit in programming and I generally double-check to include the header files for all functions I use.

If you do this when compiling in 64-bit mode, your returned pointer will be truncated to 32-bits.

EDIT: Sorry for being too brief. Here's an example code fragment for discussion purposes.

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

Suppose that the returned heap pointer is something bigger than what is representable in an int, say 0xAB00000000.

If malloc is not prototyped to return a pointer, the int value returned will initially be in some register with all the significant bits set. Now the compiler say, "okay, how do I convert and int to a pointer". That's going to be either a sign extension or zero extension of the low order 32-bits that it has been told malloc "returns" by omitting the prototype. Since int is signed I think the conversion will be sign extension, which will in this case convert the value to zero. With a return value of 0xABF0000000 you'll get a non-zero pointer that will also cause some fun when you try to dereference it.

A Reusable Software Rule:

In the case of writing an inline function in which used malloc(), in order to make it reusable for C++ code too, please do an explicit type casting (e.g. (char*)); otherwise compiler will complain.

One of the good higher-level arguments against casting the result of malloc is often left unmentioned, even though, in my opinion, it is more important than the well-known lower-level issues (like truncating the pointer when the declaration is missing).

A good programming practice is to write code, which is as type-independent as possible. This means, in particular, that type names should be mentioned in the code as little as possible or best not mentioned at all. This applies to casts (avoid unnecessary casts), types as arguments of sizeof (avoid using type names in sizeof) and, generally, all other references to type names.

Type names belong in declarations. As much as possible, type names should be restricted to declarations and only to declarations.

From this point of view, this bit of code is bad

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

and this is much better

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

not simply because it "doesn't cast the result of malloc", but rather because it is type-independent (or type-agnositic, if you prefer), because it automatically adjusts itself to whatever type p is declared with, without requiring any intervention from the user.

A void pointer in C can be assigned to any pointer without an explicit cast. The compiler will give warning but it can be reusable in C++ by type casting malloc() to corresponding type. With out type casting also it can be use in C, because C is no strict type checking. But C++ is strictly type checking so it is needed to type cast malloc() in C++.

The malloc() function could often require a conversion cast before. For the returned type from malloc it is a pointer to void and not a particular type, like may be a char* array, or a string. And sometimes the compiler could not know, how to convert this type.

int size = 10;
char* pWord = (char*)malloc(size);

The allocation functions are available for all C packages. So, these are general functions, that must work for more C types. And the C++ libraries are extensions of the older C libraries. Therefore the malloc function returns a generic void* pointer.

Cannot allocate an object of a type with another of different type. Unless the objects are not classes derived from a common root class. And not always this is possible, there are different exceptions. Therefore a conversion cast might be necessary in this case.

Maybe the modern compilers know how to convert different types. So this could not be a great issue, when is doing this conversion. But a correct cast can be used, if a type conversion is possible. As an example: it cannot be cast "apples" to "strawberries". But these both, so called "classes", can be converted to "fruits".

There are custom structure types, which cannot be cast directly. In this case, any member variable has to be assigned separately. Or a custom object would have to set its members independently. Either if it is about a custom object class, or whatever else...

Also a cast to a void pointer must be used when using a free call. This is because the argument of the free function is a void pointer.

free((void*)pWord);

Casts are not bad, they just don't work for all the variable types. A conversion cast is also an operator function, that must be defined. If this operator is not defined for a certain type, it may not work. But not all the errors are because of this conversion cast operator.

With kind regards, Adrian Brinas