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

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

而不是:

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

为什么会这样?


当前回答

我更喜欢演演员,但不是手动。我最喜欢使用glib中的g_new和g_new0宏。如果不使用glib,我会添加类似的宏。这些宏在不损害类型安全的情况下减少代码重复。如果类型错误,将在非空指针之间进行隐式转换,这将导致警告(C++中的错误)。如果忘记包含定义g_new和g_new0的标头,则会出现错误。gnew和gnew0都采用相同的参数,不像malloc采用比calloc更少的参数。只需添加0即可获得零初始化内存。该代码可以用C++编译器编译而无需更改。

其他回答

在C语言中,可以将空指针分配给任何指针,这就是为什么不应该使用类型转换的原因。如果您想要“类型安全”分配,我可以推荐以下宏函数,这些函数我总是在C项目中使用:

#include <stdlib.h>
#define NEW_ARRAY(ptr, n) (ptr) = malloc((n) * sizeof *(ptr))
#define NEW(ptr) NEW_ARRAY((ptr), 1)

有了这些,你可以简单地说

NEW_ARRAY(sieve, length);

对于非动态数组,第三个必须的函数宏是

#define LEN(arr) (sizeof (arr) / sizeof (arr)[0])

这使得阵列环路更加安全和方便:

int i, a[100];

for (i = 0; i < LEN(a); i++) {
   ...
}

我更喜欢演演员,但不是手动。我最喜欢使用glib中的g_new和g_new0宏。如果不使用glib,我会添加类似的宏。这些宏在不损害类型安全的情况下减少代码重复。如果类型错误,将在非空指针之间进行隐式转换,这将导致警告(C++中的错误)。如果忘记包含定义g_new和g_new0的标头,则会出现错误。gnew和gnew0都采用相同的参数,不像malloc采用比calloc更少的参数。只需添加0即可获得零初始化内存。该代码可以用C++编译器编译而无需更改。

在C语言中,可以隐式将void指针转换为任何其他类型的指针,因此不需要强制转换。使用一个可能会向不经意的观察者暗示需要一个的原因,这可能会误导人。

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

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的返回值是合理的。

尽可能在C语言中编程时最好做的事情是:

通过C编译器编译程序,并打开所有警告-Wall并修复所有错误和警告确保没有声明为auto的变量然后使用带有-Wall和-std=C++11的C++编译器对其进行编译。修复所有错误和警告。现在再次使用C编译器进行编译。您的程序现在应该在没有任何警告的情况下编译,并且包含更少的错误。

这个过程使您可以利用C++严格的类型检查,从而减少错误的数量。特别是,此过程强制您包含stdlib.否则您将得到

malloc未在此范围内声明

并且还强制你使用malloc的结果,否则你会得到

从void*到T的转换无效*

或者你的目标类型是什么。

用C而不是C++编写的唯一好处是

C具有明确规定的ABIC++可能会生成更多代码[异常、RTTI、模板、运行时多态性]

请注意,在理想情况下,当使用C所共有的子集和静态多态特性时,第二个缺点应该消失。

对于那些觉得C++严格规则不方便的人,我们可以使用带有推断类型的C++11特性

auto memblock=static_cast<T*>(malloc(n*sizeof(T))); //Mult may overflow...