c - 为什么先将 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
在计算完成之前扩大到防止有符号整数溢出,如这里和这里所说,但我不太明白它是如何工作的。
解决方案
当您sizeof
首先编写操作时,您通常会确保至少使用size_t
数学来完成计算。让我们确定这意味着什么。
最后放置sizeof(X)
的问题:
想象一下这个场景,h
有价值200000
,w
有价值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 );
,您通常不会有发生整数溢出的风险。
这是因为两个原因。
sizeof
获得一个无符号整数类型的值size_t
。size_t
在最现代的实现中,整数转换等级和大小比int
. 共同值:sizeof(size_t) == 8
和sizeof(int) == 4
。这对于第 2 点很重要,它在算术表达式中发生称为整数提升(算术转换)。
在表达式中经常发生操作数的自动类型转换。这称为整数或隐式类型提升。有关这方面的更多信息,您可以查看这个有用的常见问题解答。
对于此提升,整数类型的转换等级很重要,因为较低整数转换等级的操作数类型被提升为较高整数转换等级的操作数类型。
查看 C 标准中的确切短语:
“否则,如果两个操作数都具有有符号整数类型或都具有无符号整数类型,则具有较小整数转换等级的类型的操作数将转换为具有较大等级的操作数的类型。”
资料来源:C18,§6.3.1.8/1
符号的转换也可以在这里发生,这在这种情况下很重要,稍后将描述。
“否则,如果无符号整数类型的操作数的等级大于或等于另一个操作数类型的等级,则将有符号整数类型的操作数转换为无符号整数类型的操作数的类型。”
……
“否则,如果带符号整数类型的操作数的类型可以表示无符号整数类型的操作数类型的所有值,则将无符号整数类型的操作数转换为有符号整数类型的操作数的类型。 "
资料来源:C18,§6.3.1.8/1
如果
size_t
具有高于或至少等于整数转换等级int
并且int
不能表示的所有值size_t
(这是满足的,因为int
通常具有比size_t
前面所说的更小的大小),则操作数h
和w
类型在计算之前int
被提升为类型。size_t
有符号转换为无符号整数的重要性:
现在您可能会问:为什么将有符号转换为无符号整数很重要?
这里还有两个原因,其中第二个更重要,但为了完整起见,我想同时涵盖这两个原因。
无符号整数始终具有比具有相同整数转换等级的有符号整数更宽的正范围。这是因为有符号整数也总是需要表示一个负值范围。无符号整数没有负范围,因此可以表示的正值几乎是有符号整数的两倍。
但更重要的是:
“涉及无符号操作数的计算永远不会溢出,因为无法由结果无符号整数类型表示的结果会以比结果类型可以表示的最大值大一的数字为模减少。”
资料来源:C18,§6.2.5/9(强调我的)
这就是为什么将sizeof
操作放在首位malloc( sizeof(*p) * h * w );
更安全的原因。
但是,在这种情况下,您将通过使用无符号整数来克服限制,因为环绕分配的内存太小而无法将其用于所需目的。访问未分配的内存也会调用未定义的行为。
但尽管如此,它可以保护您在调用malloc()
自身时不会出现未定义的行为。
旁注:
请注意,放在
sizeof
第二个位置malloc( h * sizeof(*p) * w )
在技术上会达到相同的效果,尽管它可能会降低可读性。如果调用中的算术表达式
malloc()
只有一个或两个操作数(例如sizeof(x)
和 anint
),则顺序无关紧要。但是为了遵守约定,我建议使用相同的样式,将sizeof()
始终放在首位:malloc(sizeof(int) * 4)
. 这样,您就不会因为有 2 个int
操作数而意外忘记它。使用像
size_t
forh
and这样的无符号整数类型w
也可以是更聪明的选择。它确保首先不会发生未定义的溢出,而且它更适合h
并且w
不意味着具有负值。
有关的:
推荐阅读
- css - 如何强制“img”标签尊重父“图片”参数?
- python - 计算熊猫数据框的滚动相关性
- sqlite - 使用绑定参数转义 SQLite 查询中的查询
- python - Python pickle 无法保存包含文本和 numpy 数组的 3g 大小字典
- python - 检查分配给最初设置为 None 的保护值的属性注释
- node.js - 如何在 joi 验证中使用相同的键来验证不同的数据类型
- emscripten - 如何使用 clang 9 安装 Emscripten?
- python - Calling Python code from TCL and checking its return value
- xero-api - Xero 的 OAuth 2.0 支持哪些 OAuth2.0 流程?
- jquery - Bootstrap 模态对话框是通过 JQuery 打开时自动滚动浏览器到顶部