提问



在这个问题中,有人在评论中建议我投出malloc的结果,即


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


而不是:


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


为什么会这样呢?

最佳参考


;你不投下结果,因为:



  • 这是不必要的,因为在这种情况下void *被自动安全地提升为任何其他指针类型。

  • 它增加了代码的混乱,强制转换不易读取(特别是如果指针类型很长)。

  • 这让你重复自己,这通常很糟糕。

  • 如果您忘记包含,它可以隐藏错误。这可能会导致崩溃(或者更糟糕的是,不导致崩溃,直到稍后在代码的某些完全不同的部分中)。考虑如果指针和整数的大小不同会发生什么;然后你通过强制转换来隐藏警告并且可能会丢失你返回的地址。注意:从C11开始,隐式函数从C中消失了,这一点不再相关,因为没有自动假设未声明的函数返回int



作为澄清,请注意我说你不投,而不是你不需要施放。在我看来,即使你做对了也没有包括演员。没有任何好处,但是一堆潜在的风险,包括演员表明你不知道风险。


还要注意,正如评论员指出的那样,上面谈到的是直C,而不是C ++。我非常坚信C和C ++是不同的语言。


要进一步添加,您的代码会不必要地重复可能导致错误的类型信息(int)。最好取消引用用于存储返回值的指针,将两者锁定在一起:


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


这也将length移到前面以增加可见性,并用sizeof删除多余的括号;当参数是类型名称时,它们只需要 。许多人似乎不知道(或忽略)这一点,这使得他们的代码更加冗长。记住:sizeof不是一个功能! :)





虽然在某些极少数情况下将length移动到前面可能可以提高可见性,但是在一般情况下,应该注意将表达式编写为:


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


由于首先保持sizeof,在这种情况下,确保乘法至少用size_t数学完成。


比较:malloc(sizeof *sieve * length * width)malloc(length * width * sizeof *sieve)widthlength小于size_t时,第二个可能溢出length * width

其它参考1


在C中,您不需要转换malloc的返回值。malloc返回的void指针会自动转换为正确的类型。但是,如果您希望您的代码使用C ++编译器,需要强制转换。社区中的首选替代方法是使用以下代码:


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


如果您改变sieve的类型,这使您不必担心改变表达式的右侧。


人们已经指出,演员阵容很糟糕。特别是指针转换。

其它参考2


执行强制转换,因为:



  • 它使您的代码在C和C ++之间更具可移植性,并且正如SO经验所示,许多程序员声称他们在用C ++(或C plus本地编译器)编写时正在用C语言编写扩展)。

  • 如果没有这样做可以隐藏错误:请注意所有混淆何时写type *type **的例子。

  • 它让你注意到[[/42]]一个合适的头文件没有注意到森林树木的想法。这就像说不要担心你没有要求编译器抱怨没有看到原型这一事实一样 - 那令人讨厌的stdlib.h是真正重要的事情要记住!

  • 强制进行额外的认知交叉检查。它将(声称的)所需类型放在你正在为该变量的原始大小做的算术旁边。我打赌你可以做一个SO研究,表明当[[sa]]时malloc()错误被捕获得更快投。与断言一样,显示意图的注释可以减少错误。

  • 以机器可以检查的方式重复自己通常是伟大的主意。事实上,这就是一个断言,并且这种使用强制转换是一种断言。断言仍然是我们获得正确代码的最常用技术,因为图灵在很多年前提出了这个想法。


其它参考3


正如其他人所说,C不是必需的,而是C ++。如果您认为要使用C ++编译器编译C代码,出于某种原因,您可以使用宏,例如:


#ifdef __cplusplus
# define NEW(type, count) ((type *)calloc(count, sizeof(type)))
#else
# define NEW(type, count) (calloc(count, sizeof(type)))
#endif


这样你仍然可以以非常紧凑的方式编写它:


int *sieve = NEW(int, 1);


它将为C和C ++编译。

其它参考4


来自维基百科[107]



??投射的优势

??
??

    ??
  • 包含强制转换可能允许C程序或函数编译为C ++。

  • ??
  • 该演员版允许1989年之前版本的malloc最初返回char *。

  • ??
  • 如果目标指针类型发生变化,则Casting可以帮助开发人员识别类型大小调整的不一致性,特别是如果指针远离malloc()调用(尽管现代编译器和静态分析器可以在不需要强制转换的情况下警告此类行为) 。

  • ??

??
??投射的缺点

??
??

    ??
  • 根据ANSI C标准,演员表是多余的。

  • ??
  • 添加强制转换可能会掩盖未包含标题 stdlib.h 的错误
    ??找到了malloc的原型。在没有
    ??对于malloc的原型,标准要求C编译器
    ??假设malloc返回一个int。如果没有演员表,则会发出警告
    ??将此整数赋给指针时发出;然而,有
    ??演员,这个警告没有产生,隐藏了一个bug。一定的
    ??体系结构和数据模型(例如64位系统上的LP64,其中
    ??long和指针是64位,int是32位),这个错误可以
    ??实际上会导致未定义的行为,如隐式声明的那样
    ??malloc返回32位值,而实际定义的函数
    ??返回64位值。取决于调用约定和内存
    ??布局,这可能会导致堆栈粉碎。这个问题不太可能发生
    ??在现代编译器中被忽视,因为它们统一产生
    ??警告已使用未声明的功能,因此会发出警告
    ??仍然出现。例如,GCC的默认行为是显示一个
    ??警告,显示内置的不兼容的隐式声明
    ??功能无论演员是否存在。

  • ??
  • 如果指针的类型在其声明处被更改,则可以
    ??另外,需要更改调用malloc和cast的所有行。

  • ??



虽然没有强制转换的malloc是首选方法,而且大多数有经验的程序员选择,但您应该使用您想知道的问题。


即:如果你需要将C程序编译为C ++(虽然这些是单独的语言),你应该使用malloc进行强制转换。

其它参考5


在C中,您可以隐式地将void指针转换为任何其他类型的指针,因此不需要强制转换。使用一个可能会向不经意的观察者建议,有一些原因需要一个,这可能会产生误导。

其它参考6


你不会抛出malloc的结果,因为这样做会给代码带来无意义的混乱。


人们投射malloc结果的最常见原因是因为他们不确定C语言的工作原理。这是一个警告信号:如果你不知道特定的语言机制是如何工作的,那么就不会猜测。在Stack Overflow上查找或询问。


一些评论:



  • 无需显式转换(C11 6.3.2.3和6.5.16.1),可以将void指针转换为任何其他指针类型。

  • 但是,C ++不允许在void*和另一个指针类型之间进行隐式转换。所以在C ++中,演员阵容是正确的。但是如果你用C ++编程,你应该使用new而不是malloc()。而且你永远不应该使用C ++编译器编译C代码。


    如果需要使用相同的源代码支持C和C ++,请使用编译器开关标记差异。不要尝试使用相同的代码来区分两种语言标准,因为它们不兼容。

  • 如果C编译器由于忘记包含标头而无法找到函数,则会收到编译器/链接器错误。所以,如果你忘了包含,那就没什么大不了的,你就不能建立自己的程序。

  • 对于遵循超过25年的标准版本的古代编译器,忘记包含会导致危险的行为。因为在那个古老的标准中,没有可见原型的函数隐式地将返回类型转换为int。然后显式地从malloc中转换结果将隐藏此错误。


    但这确实不是问题。你没有使用25年的电脑,为什么你会使用25年的编译器?


其它参考7


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

其它参考8


现在没有必要转换malloc()返回的值,但我想补充一点,似乎没有人指出:


在古代,也就是说,在 ANSI C 提供void *作为通用类型的指针之前,char *是这种用法的类型。在这种情况下,强制转换可以关闭编译器警告。


参考:C FAQ [108]

其它参考9


不必强制转换malloc的结果,因为它返回void*,并且void*可以指向任何数据类型。

其它参考10


只是添加我的经验,亚博2018平台计算机工程我看到我在C中写过的两三位教授总是施放malloc,但是我问的那个(有着巨大的简历和对C的理解)告诉我这绝对是不必要的但是以前只是绝对具体,并让学生进入绝对具体的心态。本质上,cast不会改变它的工作方式,它完全按照它所说的那样,分配内存,并且转换不会影响它,你获得相同的内存,即使你错误地将它转换为其他内容(并以某种方式避免编译器)错误)C将以相同的方式访问它。


?铸造有一定的意义。当您使用数组表示法时,生成的代码必须知道它有多少内存位置才能到达下一个元素的开头,这是通过强制转换实现的。通过这种方式,你知道对于一个双倍,你前进8个字节,而对于一个int,你去4,依此类推。因此,如果使用指针表示法则无效,在数组表示法中它变得必要。

其它参考11


返回的类型为void *,可以将其强制转换为所需类型的数据指针,以便可以解除引用。

其它参考12


void指针是一个通用指针,C支持从void指针类型到其他类型的隐式转换,因此不需要显式地对其进行类型转换。


但是,如果您希望相同的代码在C ++平台上完全兼容(不支持隐式转换),则需要进行类型转换,因此这一切都取决于可用性。

其它参考13


在此处添加所有信息;这就是GNU C Library Reference手册所说的:[109]



??您可以将malloc的结果存储到任何没有a的指针变量中
??转换,因为ISO C自动将类型void *转换为另一个
??必要时指针的类型。但演员阵容在上下文中是必要的
??除了赋值运算符或者您可能希望运行代码
??在传统的C.



事实上,ISO C11标准(p347)是这样说的:[110]



??如果分配成功,则返回指针
??它可以被分配给指向任何类型的对象的指针
??基本对齐要求,然后用于访问这样的
??对象或分配的空间中的此类对象的数组(直到
??空间被明确释放)


其它参考14


这取决于编程语言和编译器。如果在C中使用malloc,则无需键入强制转换,因为它会自动键入强制转换,但是如果你使用C ++则应该输入强制转换,因为malloc将返回void*类型。

其它参考15


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


#include 
#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++) {
   ...
}

其它参考16


转换仅适用于C ++而不是C.如果您使用的是C ++编译器,最好将其更改为C编译器。

其它参考17


过去GCC和Clang的人都被宠坏了。那里并不是那么好。


多年来,我一直被我需要使用的令人震惊的老化编译器吓坏了。通常公司和经理采用极端保守的方法来更改编译器,如果是新的编译器,甚至不会测试 (具有更好的标准兼容性和代码优化)将在他们的系统中工作。对于工作开发人员来说,实际的现实是,当你进行编码时,你需要覆盖你的基础,不幸的是,如果你无法控制什么编译器,那么铸造mallocs是一个好习惯可能会应用于您的代码。


我还建议许多组织应用自己的编码标准, 在没有明确指导的情况下,我倾向于最有可能在任何地方进行编译,而不是盲目遵守标准。


在现行标准下不必要的论点是非常有效的。但是这个论点忽略了现实世界的实际情况。我们不是在一个完全由当时标准统治的世界中编码,而是在我喜欢的实际情况下编码称之为地方管理的现实领域。而那个弯曲和扭曲的时间比空间时间更长。:-)


因人而异。


我倾向于将malloc视为防御性操作。不漂亮,不完美,但一般都很安全。 (老实说,如果你没有包含stdlib.h,那么你比使用malloc更多的问题方式了!)。

其它参考18


我只是为了表示不赞成类型系统中的丑陋洞而放入演员表,这允许在没有诊断的情况下编译下面的代码片段,即使没有使用强制转换来导致错误的转换:


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


我希望它不存在(并且它不会在C ++中存在),所以我投了。它代表了我的品味和我的编程政治。我不仅要投掷指针,而且要有效地投票,并投出愚蠢的恶魔。如果我不能实际愚蠢,那么至少让我表达希望做的事情所以以抗议的姿态。[111] [112]


事实上,一个好的做法是将malloc(和朋友)包含在返回unsigned char *的函数中,并且基本上永远不会在代码中使用void *。如果你需要一个通用指针到任意对象,使用char *unsigned char *,并在两个方向上进行转换。可能放纵的一种放松可能是使用memsetmemcpy等函数而不使用强制转换。


关于转换和C ++兼容性的主题,如果你编写代码使它编译为C和C ++(在这种情况下你必须在分配它时转换malloc的返回值除了void *之外的其他东西,你可以为自己做一个非常有用的事情:你可以使用宏进行转换,在编译为C ++时转换为C ++样式转换,但在编译为C时减少为C转换:


/* In a header somewhere */
#ifdef __cplusplus
#define strip_qual(TYPE, EXPR) (const_cast(EXPR))
#define convert(TYPE, EXPR) (static_cast(EXPR))
#define coerce(TYPE, EXPR) (reinterpret_cast(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删除constvolatile,但程序更改的方式现在涉及类型转换,您将获得诊断,并且你将不得不使用强制转换的组合来获得所需的转换。


为了帮助您遵守这些宏,GNU C ++(而不是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-style-cast选项找出可能蔓延到代码中的所有(type)转换语法,并通过替换它来跟进这些诊断从上述宏中适当选择(或必要时组合)。


这种对转换的处理是在清除C中工作的单一最大的独立技术理由:C和C ++组合的方言,在技术上证明了铸造malloc的返回值。

其它参考19


void指针背后的概念是它可以被转换为任何数据类型,这就是malloc返回void的原因。您还必须了解自动类型转换。因此,必须执行此操作并非强制转换指针。它有助于保持代码清除并有助于调试

其它参考20


只要有可能,在C语言编程时最好的事情是:



  1. 使您的程序通过C编译器进行编译,并打开所有警告-Wall并修复所有错误和警告

  2. 确保没有变量声明为auto

  3. 然后使用带有-Wall-std=c++11的C ++编译器进行编译。修复所有错误和警告。

  4. 现在再次使用C编译器进行编译。您的程序现在应该在没有任何警告的情况下编译,并且包含更少的错误。



此过程允许您利用C ++严格类型检查,从而减少错误的数量。特别是,这个程序强迫你包括stdlib.h或者你会得到



??malloc未在此范围内宣布



并强迫你施放malloc的结果,否则你会得到



??从void*T*的无效转换



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


用C而不是C ++编写的唯一好处是我能找到的



  1. C具有良好指定的ABI

  2. C ++可能会生成更多代码[[例外,RTTI,模板,运行时多态]]



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


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


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

其它参考21


不,你没有投出malloc()的结果。



一般情况下,您不会[[或void *


不这样做的典型原因是#include 的失败可能会被忽视。由于C99使隐式函数声明非法,因此这不再是一个问题,因此如果您的编译器符合至少C99,您将获得诊断消息。


但是有一个更强大的理由不引入不必要的指针转换:


在C中,指针强制转换几乎总是错误。这是因为以下规则(N1570中的§6.5p7,C11的最新草案):



??对象的存储值只能由具有其中一个的左值表达式访问
??以下类型:

?? - 与对象的有效类型兼容的类型,

?? - 与对象的有效类型兼容的类型的限定版本,

?? - 对应于有效类型的有符号或无符号类型的类型
??对象,结果
?? - 对应于合格版本的有符号或无符号类型的类型
??有效的对象类型,

?? - 聚合或联合类型at包括其中一种上述类型
??成员(包括,递归地,子集合或包含的联合的成员)或

?? - 一个字符类型。



这也称为严格别名规则。所以以下代码是未定义的行为:


long x = 5;
double *p = (double *)&x;
double y = *p;


而且,有时令人惊讶的是,以下情况也是如此:


struct foo { int x; };
struct bar { int x; int y; };
struct bar b = { 1, 2};
struct foo *p = (struct foo *)&b;
int z = p->x;


有时候,你需要转换指针,但鉴于严格别名规则,你必须非常小心。因此,在您的代码中出现任何指针都是必须仔细检查其有效性的地方。因此,您永远不会编写不必要的指针转换。


TL;博士



简而言之:因为在C中,任何出现的指针转换应该为需要特别注意的代码引发一个红色标记,所以你永远不应该写不必要的指针演员。





附注:



  • 在某些情况下,您实际上需要转换为void *,例如如果要打印指针:


    int x = 5;
    printf("%p\n", (void *)&x);
    


    这里需要强制转换,因为printf()是一个可变函数,所以隐式转换不起作用。

  • 在C ++中,情况有所不同。在处理派生类的对象时,转换指针类型有点常见(并且正确)。因此,在C ++中,与void *之间的转换是而不是隐式是有道理的。 C ++有一整套不同的铸造风格。


其它参考22


我喜欢做演员,但不是手动。我最喜欢的是使用来自glib的g_newg_new0宏。如果没有使用glib,我会添加类似的宏。这些宏减少了代码重复,而不会影响类型安全性。如果你得到错误的类型,你会得到非void指针之间的隐式转换,这会引起警告(C ++中的错误)。如果您忘记包含定义g_newg_new0的标题,则会出现错误。 g_newg_new0都采用相同的参数,不像malloc参数少于calloc。只需添加0即可获得零初始化内存。代码可以使用C ++编译器进行编译而无需更改。

其它参考23


void指针是一个通用指针,C支持从void指针类型到其他类型的隐式转换,因此不需要显式地对其进行类型转换。


但是,如果您希望相同的代码在C ++平台上完全兼容(不支持隐式转换),则需要进行类型转换,因此这一切都取决于可用性。

其它参考24



  1. 如其他所述,C不需要,但C ++需要。

  2. 包含强制转换可能允许C程序或函数编译为C ++。

  3. 在C中没有必要,因为void *会自动安全地提升为任何其他指针类型。

  4. 但是如果你投了那么,如果你忘记包括它,它可以隐藏错误
    stdlib.h中即可。这可能会导致崩溃(或者更糟糕的是,不会导致崩溃
    直到稍后在代码的一些完全不同的部分)。


    因为 stdlib.h 包含找到malloc的原型。在里面
    缺少malloc的原型,标准要求C
    编译器假定malloc返回一个int。如果没有演员,a
    将此整数分配给指针时发出警告;
    然而,对于演员表,这个警告不会产生,隐藏了一个bug。


其它参考25


在C中不必转换malloc,但在C ++中是必需的。



  • 在C中不需要进行强制转换,因为在这种情况下,void *会自动安全地提升为任何其他指针类型。

  • 如果您忘记包含,它可以隐藏错误。这可能会导致崩溃。

  • 如果指针和整数的大小不同,那么您将通过强制转换隐藏警告,并可能丢失返回地址的位数。


其它参考26


请帮助自己一个忙,更重要的是帮助下一个维护代码的人,并尽可能多地提供有关程序变量数据类型的信息。


因此,castmalloc返回指针。在下面的代码中,编译器可以确保筛子实际上被分配了一个整数的点。


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


当/如果筛的数据类型改变时,这减少了人为错误的机会。


我有兴趣知道是否有任何纯C编译器会将此语句标记为错误。如果是这样,请告诉我,以便我可以避免它们,因为缺乏类型检查会增加维护软件的总体费用。