在这个问题上,有人在评论中建议我不要使用malloc的结果。即,我应该这样做:

int *sieve = malloc(sizeof(*sieve) * length);

而不是:

int *sieve = (int *) malloc(sizeof(*sieve) * length);

为什么会这样?


当前回答

malloc的主要问题是获得正确的大小。

malloc()返回的内存是非类型的,由于简单的强制转换,它不会神奇地获得有效的类型。

我想这两种方法都很好,选择应该取决于程序员的意图。

如果为类型分配内存,则使用强制转换。

ptr=(T*)malloc(sizeof(T));

如果为给定指针分配内存,则不要使用强制转换。

ptr=malloc(sizeof*ptr);

Ad 1

第一种方法通过为给定类型分配内存,然后将其强制转换以确保分配给正确的指针,从而确保正确的大小。如果使用了不正确的ptr类型,则编译器将发出警告/错误。如果ptr的类型被更改,那么编译器将指出代码需要重构的地方。

此外,第一种方法可以组合成类似于C++中新运算符的宏。

#define NEW(T) ((T*)malloc(sizeof(T)))
...
ptr = NEW(T);

此外,如果ptr为void*,则此方法有效。

Ad 2

第二个方法不关心类型,它通过从指针的类型中获取数据来确保正确的大小。这种方法的主要优点是,每当ptr的类型发生变化时,都可以自动调整存储大小。它可以在重构时节省一些时间(或错误)。

缺点是,如果ptr为空,则该方法不起作用,但它可能被认为是一件好事。而且它不适用于C++,因此不应在C++程序将要使用的头中的内联函数中使用。

就我个人而言,我更喜欢第二种选择。

其他回答

加上我学习计算机工程的经验,我发现我见过的两三位教授总是用C语言写作,但我问的那位教授(有着丰富的简历和对C语言的理解)告诉我,这是绝对没有必要的,但只用于绝对具体,并让学生们养成绝对具体的心态。从本质上讲,强制转换不会改变它的工作方式,它完全按照它所说的做,分配内存,强制转换不影响它,你会得到相同的内存,即使你错误地将它强制转换为其他东西(并以某种方式规避编译器错误),C也会以同样的方式访问它。

编辑:铸造有一定的意义。当您使用数组表示法时,生成的代码必须知道要到达下一个元素的开头,需要前进多少个内存位置,这是通过强制转换实现的。通过这种方式,您可以知道,对于double,您可以提前8个字节,而对于int,则可以提前4个字节,依此类推。因此,如果您使用指针表示法,则不会产生任何影响,在数组表示法中,这是必要的。

在C语言中,您得到了从void*到任何其他(数据)指针的隐式转换。

习惯于GCC和Clang的人被宠坏了。外面不是很好。

这些年来,我被要求使用的过时的编译器吓坏了。公司和管理者通常采用极端保守的方法来更改编译器,甚至不会测试新的编译器(具有更好的标准遵从性和代码优化)是否能在他们的系统中工作。对于正在工作的开发人员来说,实际情况是,当你在编写代码时,你需要覆盖你的基础,不幸的是,如果你不能控制什么编译器可以应用于你的代码,那么使用mallocs是一个好习惯。

我还建议许多组织应用自己的编码标准,如果定义了编码标准,那么这应该是人们遵循的方法。在缺乏明确指导的情况下,我倾向于选择最有可能在任何地方编译,而不是盲目地遵守某个标准。

根据现行标准,这是不必要的,这一论点是非常有效的。但这一论点忽略了现实世界的现实性。在一个完全由当今标准统治的世界里,我们并没有编码,而是由我喜欢称之为“地方管理的现实领域”的实用性来控制。这比太空时代更加弯曲和扭曲

YMMV。

我倾向于将使用malloc作为防御操作。不漂亮,不完美,但总体安全。(老实说,如果你没有包含stdlib.h,那么你会遇到比强制执行malloc更多的问题!)。

您不需要强制转换malloc的结果,因为这样做会给代码增加无意义的混乱。

人们之所以选择malloc的结果,最常见的原因是他们不确定C语言是如何工作的。这是一个警告信号:如果你不知道特定的语言机制是如何工作的,那么就不要猜测。查找或询问堆栈溢出。

一些意见:

空指针可以转换为任何其他指针类型,而无需显式转换(C11 6.3.2.3和6.5.16.1)。但是,C++不允许在void*和另一个指针类型之间进行隐式转换。因此,在C++中,演员阵容是正确的。但是如果你用C++编程,你应该使用new而不是malloc()。而且您永远不应该使用C++编译器编译C代码。如果需要使用相同的源代码同时支持C和C++,请使用编译器开关来标记差异。不要试图用相同的代码来满足两种语言标准,因为它们不兼容。如果C编译器因为忘记包含标头而找不到函数,则会出现编译器/链接器错误。因此,如果您忘记包含<stdlib.h>,这没什么大不了的,您将无法构建您的程序。在遵循超过25年的标准版本的古代编译器上,忘记包含<stdlib.h>将导致危险行为。因为在那个古老的标准中,没有可见原型的函数隐式地将返回类型转换为int。显式地从malloc转换结果将隐藏这个错误。但这真的不是问题。你不是在使用一台25年前的计算机,那么你为什么要使用一台有25年历史的编译器呢?

我加入强制转换只是为了表示对类型系统中的丑陋漏洞的不满,这种漏洞允许像以下代码片段这样的代码在没有诊断的情况下编译,即使没有强制转换来导致错误的转换:

double d;
void *p = &d;
int *q = p;

我希望这不存在(而且在C++中也不存在),所以我投了。它代表了我的品味和我的编程政治。我不仅投了一个指针,而且有效地投了一张选票,并淘汰了愚蠢的恶魔。如果我真的不能摆脱愚蠢,那么至少让我用一种抗议的姿态来表达这样做的愿望。

事实上,一个好的做法是用返回无符号char*的函数包装malloc(和朋友),并且基本上不要在代码中使用void*。如果需要指向任何对象的通用指针,请使用char*或无符号char*,并在两个方向上进行强制转换。也许,可以放松的一点是使用memset和memcpy等函数而不进行强制转换。

关于强制转换和C++兼容性的主题,如果您编写代码以使其同时编译为C和C++(在这种情况下,当将malloc的返回值分配给void*以外的其他对象时,您必须强制转换malloc),您可以为自己做一件非常有用的事情:当编译为C++时,可以使用宏强制转换为C++风格的强制转换,但当编译为C:

/* In a header somewhere */
#ifdef __cplusplus
#define strip_qual(TYPE, EXPR) (const_cast<TYPE>(EXPR))
#define convert(TYPE, EXPR) (static_cast<TYPE>(EXPR))
#define coerce(TYPE, EXPR) (reinterpret_cast<TYPE>(EXPR))
#else
#define strip_qual(TYPE, EXPR) ((TYPE) (EXPR))
#define convert(TYPE, EXPR) ((TYPE) (EXPR))
#define coerce(TYPE, EXPR) ((TYPE) (EXPR))
#endif

如果您遵循这些宏,那么对代码库进行简单的grep搜索以查找这些标识符将显示所有强制转换的位置,因此您可以检查其中是否有错误。

然后,如果您经常使用C++编译代码,那么它将强制使用适当的强制转换。例如,如果您使用strip_qual只是为了删除常量或volatile,但是程序的变化导致现在涉及到类型转换,那么您将得到一个诊断,并且您必须使用强制转换的组合来获得所需的转换。

为了帮助您遵守这些宏,GNUC++(而不是C!)编译器有一个漂亮的功能:一个可选的诊断程序,它针对所有C样式强制转换的出现而生成。

     -Wold-style-cast (C++ and Objective-C++ only)
         Warn if an old-style (C-style) cast to a non-void type is used
         within a C++ program.  The new-style casts (dynamic_cast,
         static_cast, reinterpret_cast, and const_cast) are less vulnerable
         to unintended effects and much easier to search for.

如果您的C代码编译为C++,则可以使用此-Wold样式的转换选项来查找可能在代码中出现的(类型)转换语法的所有情况,并通过使用上述宏中的适当选项(或组合,如果需要)来替换它来跟踪这些诊断。

这种转换处理是在“Clean C”中工作的唯一最大的独立技术理由:结合了C和C++方言,这反过来又在技术上证明了强制转换malloc的返回值是合理的。