如何解决为什么先将sizeof放在malloc中更安全? 最后放置sizeof(X)的问题:首先放置int:有符号度转换为无符号整数的重要性:相关:
两者之间有什么区别
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
here和here,但我不太了解它是如何工作的。
解决方法
首先编写sizeof
操作时,通常要确保至少使用size_t
数学进行计算。让我们确定这意味着什么。
最后放置sizeof(X)
的问题:
想象一下该场景,h
的值为200000
,而w
的值为50000
(可能是偶然得到的)。
假设int
可以容纳的最大整数值为2147483647
,这很常见(您可以从宏INT_MAX
-标头{{1中读取的确切实现定义的值}}),两者都是<limits.h>
可以保留的合法值。
如果现在使用int
,则首先计算malloc( h * w * sizeof(*p) );
的部分,因为算术表达式的求值顺序从左到右。这样,您将得到一个有符号整数溢出,因为结果h * w
(100亿)不可能用10000000000
表示。
其中发生整数溢出的程序的行为是不确定的。 C标准甚至以整数溢出为例说明了未定义行为:
3.4.3
1种不确定的行为
使用非便携式或错误程序构造或错误数据时的行为,对此文档没有任何要求
2注释1:可能的不确定行为包括从完全忽略具有不可预知结果的情况到在翻译或程序执行过程中表现为环境特征(无论是否发行)的行为诊断消息),以终止翻译或执行(伴随诊断消息的发布)。
3注释2: J.2概述了导致未定义行为的C程序的属性。
4示例一个未定义行为的示例是整数溢出时的行为。
来源:C18,第3.4.3节
首先放置int
:
如果您首先使用sizeof(X)
操作(例如sizeof
),通常 就没有整数溢出的风险。
这有两个原因。
-
malloc( sizeof(*p) * h * w );
获得无符号整数类型sizeof
的值。size_t
在最现代的实现中具有比size_t
高的整数转换等级和大小。常用值:int
和sizeof(size_t) == 8
。这对于第2点很重要,这是在算术表达式中出现的称为“ 整数提升”(算术转换)的东西。
-
在表达式中,经常发生操作数的自动类型转换。这称为整数或隐式类型提升。有关此的更多信息,请查看此有用的FAQ。
对于此升级,整数类型的转换等级很重要,因为整数转换等级较低的操作数的类型将提升为整数转换等级较高的操作数的类型。
查看C标准中的确切短语:
“否则,如果两个操作数都具有符号整数类型或都具有无符号整数类型,则将具有较小整数转换等级的类型的操作数转换为具有较高等级的操作数的类型。”
来源:C18,第6.3.1.8/1
签名的转换也可以在这里进行,这是重要的,如后所述。
“否则,如果具有无符号整数类型的操作数的秩大于或等于另一个操作数的类型的秩,则将带符号整数类型的操作数转换为无符号整数类型的操作数的类型。 “
....
“否则,如果带符号整数类型的操作数的类型可以表示带无符号整数类型的操作数的所有值,则带无符号整数类型的操作数将转换为带符号的操作数的类型整数类型。”
来源:C18,第6.3.1.8/1
如果
sizeof(int) == 4
的整数转换等级高于或等于size_t
,并且int
不能代表int
的所有值(自size_t
的大小通常比前面提到的int
小),类型为size_t
的操作数h
和w
被提升为类型int
之前计算。
有符号度转换为无符号整数的重要性:
现在您可能会问:为什么将有符号性转换为无符号整数很重要?
这里还有两个原因使第二个更为重要,但是出于完整性考虑,我想同时覆盖这两个原因。
-
无符号整数的正范围始终比具有相同整数转换等级的有符号整数大。这是因为有符号整数也始终需要表示一个负值范围。无符号整数没有负范围,因此可以表示正值几乎是有符号整数的两倍。
但更重要的是:
“涉及无符号操作数的计算永远不会溢出,因为不能用所得的无符号整数类型表示的结果的模数要比该所得类型可以表示的最大值大一模。” / p>
来源:C18,第6.2.5 / 9节(强调我的内容)
这就是为什么像size_t
那样先放置sizeof
操作更安全的原因。
但是,在这种情况下,您将使用无符号整数来超出限制,这是因为包装的内存环绕太小而无法将其用于所需的目的。访问未分配的内存也会调用未定义的行为。
但是,尽管如此,它仍然可以保护您,防止在调用malloc( sizeof(*p) * h * w );
本身时出现不确定的行为。
旁注:
-
请注意,将
malloc()
放在第二个位置sizeof
会在技术上达到相同的效果,尽管这可能会降低可读性。 -
如果对
malloc( h * sizeof(*p) * w )
的调用中的算术表达式只有一个或2个操作数(例如malloc()
和sizeof(x)
),则顺序无关紧要。但是要遵循惯例,我建议使用相同的样式,始终将int
放在首位:sizeof()
。这样,您就不必冒险拥有2个malloc(sizeof(int) * 4)
操作数而将其遗忘了。 -
为
int
和size_t
使用h
之类的无符号整数类型也是一种更明智的选择。这样可以确保首先不会发生未定义的溢出,此外,它更合适,因为w
和h
的含义不是负值。
相关:
- How do I detect unsigned integer multiply overflow?
- Why is unsigned integer overflow defined behavior but signed integer overflow isn't?
- unsigned int vs. size_t
为什么在malloc中首先设置sizeof(* p)比最后一个位置更安全?
简单的答案是:它不(至少不应该)更安全。
更长的答案:
任何整数计算都可能溢出-一些结果的行为不确定-一些结果不正确,并且可能导致随后的程序失败。
任何整数计算都必须考虑是否会发生溢出。如果您编写的程序在单个malloc
调用中分配了超过2G的内存,那么我确定您已经意识到这一点,并确保h
和w
都具有适当的性能类型。
此外,标准并没有确切说明整数类型的最大值是多少。因此,如果要编程“安全”程序,请确保在运行时询问这些限制。
换句话说:“更安全”不是编程目标。如果您编写在边缘运行的程序,则可以使它们安全-不仅是“更安全”
,虽然乘法是可交换的,但显然编译器不会向前扫描最大的类型,sizeof()为size_t,在64位计算机上,该类型为unsigned long(2 ^ 64-1),因此顺序对于防止溢出很重要,即使所有CPU都将这些信息作为状态位提供(即使不是作为中断),它在许多语言中也会默默地发生!当然,有些程序员希望静默溢出以获取驻留的mod类型大小,但这是使我们其余人遭受痛苦的可悲原因!
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。