首页 > 解决方案 > 为什么先将 sizeof 放在 malloc 中更安全?

问题描述

和有什么区别

int *p = malloc( h * w * sizeof(*p) );

int *p = malloc( sizeof (*p) * h * w );

什么时候h和什么w类型int

sizeof(*p)为什么放在第一个位置比放在最后一个位置更安全malloc


我已经明白后一种形式用于确保size_t数学,并且int操作数将size_t在计算完成之前扩大到防止有符号整数溢出,如这里这里所说,但我不太明白它是如何工作的。

标签: cmallocsizeofsize-tinteger-promotion

解决方案


当您sizeof首先编写操作时,您通常会确保至少使用size_t数学来完成计算。让我们确定这意味着什么。


最后放置sizeof(X)的问题:

想象一下这个场景,h有价值200000w有价值50000(可能是偶然得到的)。

假设int可以容纳的最大整数值是2147483647,这是常见的(您可以从宏INT_MAX- 标头中读取的确切实现定义的值<limits.h>),两者都是int可以容纳的合法值。

如果您现在使用malloc( h * w * sizeof(*p) );,则 h * w首先计算该部分,因为算术表达式的评估顺序从左到右。有了这个,你会得到一个有符号整数溢出,因为结果10000000000(100 亿)不可能用int.

发生整数溢出的程序的行为是未定义的。C 标准将整数溢出作为其规范中未定义行为的示例:

3.4.3

1 未定义的行为

行为,在使用不可移植或错误程序构造或错误数据时,本文档对此没有要求

2 条目注 1:可能的未定义行为范围从完全忽略具有不可预测结果的情况,到在翻译或程序执行期间以环境特征的记录方式表现(有或没有发出诊断消息),到终止翻译或执行(发出诊断消息)。

3 条目注释 2: J.2 概述了导致未定义行为的 C 程序的属性。

4 示例未定义行为的一个示例是整数溢出行为。

资料来源:C18,§3.4.3


sizeof(X)而是放在第一位:

如果您sizeof先使用该操作,例如malloc( sizeof(*p) * h * w );,您通常不会有发生整数溢出的风险。

这是因为两个原因。

  1. sizeof获得一个无符号整数类型的值size_tsize_t在最现代的实现中,整数转换等级和大小比int. 共同值:sizeof(size_t) == 8sizeof(int) == 4

    这对于第 2 点很重要,它在算术表达式中发生称为整数提升算术转换)。

  2. 在表达式中经常发生操作数的自动类型转换。这称为整数隐式类型提升。有关这方面的更多信息,您可以查看这个有用的常见问题解答

    对于此提升,整数类型的转换等级很重要,因为较低整数转换等级的操作数类型被提升为较高整数转换等级的操作数类型。

    查看 C 标准中的确切短语:

    “否则,如果两个操作数都具有有符号整数类型或都具有无符号整数类型,则具有较小整数转换等级的类型的操作数将转换为具有较大等级的操作数的类型。”

    资料来源:C18,§6.3.1.8/1

    符号的转换也可以在这里发生,这在这种情况下很重要,稍后将描述。

    “否则,如果无符号整数类型的操作数的等级大于或等于另一个操作数类型的等级,则将有符号整数类型的操作数转换为无符号整数类型的操作数的类型。”

    ……

    “否则,如果带符号整数类型的操作数的类型可以表示无符号整数类型的操作数类型的所有值,则将无符号整数类型的操作数转换为有符号整数类型的操作数的类型。 "

    资料来源:C18,§6.3.1.8/1

    如果size_t具有高于或至少等于整数转换等级int并且int不能表示的所有值size_t(这是满足的,因为int通常具有比size_t前面所说的更小的大小),则操作数hw类型在计算之前int被提升为类型。size_t


有符号转换为无符号整数的重要性:

现在您可能会问:为什么将有符号转换为无符号整数很重要?

这里还有两个原因,其中第二个更重要,但为了完整起见,我想同时涵盖这两个原因。

  1. 无符号整数始终具有比具有相同整数转换等级的有符号整数更宽的正范围。这是因为有符号整数也总是需要表示一个负值范围。无符号整数没有负范围,因此可以表示的正值几乎是有符号整数的两倍。

    但更重要的是:

  2. 无符号整数永远不会溢出!

“涉及无符号操作数的计算永远不会溢出,因为无法由结果无符号整数类型表示的结果会以比结果类型可以表示的最大值大一的数字为模减少。”

资料来源:C18,§6.2.5/9(强调我的)

这就是为什么将sizeof操作放在首位malloc( sizeof(*p) * h * w );更安全的原因。

但是,在这种情况下,您将通过使用无符号整数来克服限制,因为环绕分配的内存太小而无法将其用于所需目的。访问未分配的内存也会调用未定义的行为。

但尽管如此,它可以保护您在调用malloc()自身时不会出现未定义的行为。


旁注:

  • 请注意,放在sizeof第二个位置malloc( h * sizeof(*p) * w )在技术上会达到相同的效果,尽管它可能会降低可读性。

  • 如果调用中的算术表达式malloc()只有一个或两个操作数(例如sizeof(x)和 an int),则顺序无关紧要。但是为了遵守约定,我建议使用相同的样式,将sizeof()始终放在首位:malloc(sizeof(int) * 4). 这样,您就不会因为有 2 个int操作数而意外忘记它。

  • 使用像size_tfor hand这样的无符号整数类型w也可以是更聪明的选择。它确保首先不会发生未定义的溢出,而且它更适合h并且w不意味着具有负值。


有关的:


推荐阅读