我和一些人说过这个论点,即C出界指针会导致未定义的行为,即使它们没有被解除引用.例:
int a; int *p = &a; p = p - 1;
这里的第三行将导致未定义的行为,即使p
永远不会被解除引用(*p
从未使用过).
在我看来,如果没有指针被使用,C会检查指针是否超出界限听起来有点不合逻辑(就像有人会检查街上的人,看看他们是否携带枪支以防他们进入他的房子.理想的做法是在人们即将进入房屋时对他们进行检查.我认为如果C检查,那么会发生很多运行时开销.
另外,如果C真的检查OOB指针那么为什么这不会导致UB:
int *p; // uninitialized thus pointing to a random adress
在这种情况下,即使p
指向OOB地址的机会很高,为什么也不会发生任何事情.
加:
int a; int *p = &a; p = p - 1;
说&a
是1000. p
评估第三行后的价值是:
996但仍未定义的行为,因为p
可能在其他地方取消引用并导致真正的问题.
未定义的值,这是未定义的行为.
因为我认为"第三行被称为未定义的行为"首先是因为未来可能会使用该OOB指针(解除引用),随着时间的推移,人们将其视为一种未定义的行为.现在,值p
是100%996还是未定义的行为或其值将是未定义的?
C不检查指针是否超出范围.但是当计算出落在对象边界之外的地址时,底层硬件可能会以奇怪的方式运行,指向对象结束后的异常.C标准明确地将此描述为导致未定义的行为.
对于大多数当前环境,上述代码不会造成问题,但类似的情况可能导致x86 16位保护模式中的分段错误,大约25年前.
在标准的语言中,这样的值可以是陷阱值,在不调用未定义的行为的情况下无法操纵.
C11标准的相关部分是:
6.5.6加法运算符
当一个具有整数类型的表达式被添加到指针或从指针中减去时,结果具有指针操作数的类型.如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向偏离原始元素的元素,使得结果元素和原始数组元素的下标的差异等于整数表达式.[...]如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象的最后一个元素,则评估不应产生溢出; 否则,行为未定义.如果结果指向数组对象的最后一个元素之后,则不应将其用作
*
已计算的一元运算符的操作数.
类似的未定义行为的例子是:
char *p; char *q = p;
仅仅加载未初始化指针的值会p
调用未定义的行为,即使它永远不会被解除引用.
编辑:试图争论这个问题是一个没有实际意义的问题.标准说计算这样一个地址会调用未定义的行为,所以它会这样做.某些实现可能只是计算某个值并存储它的事实是无关紧要的.不要依赖任何有关未定义行为的假设:编译器可能利用其固有的不可预测性来执行您无法想象的优化.
例如这个循环:
for (int i = 1; i != 0; i++) { ... }
可能会在没有任何测试的情况下编译成无限循环:i++
如果i
是INT_MAX
,则调用未定义的行为,因此编译器的分析如下:
初始值i
是> 0
.
对于任何正值i < INT_MAX
,i++
仍然是> 0
因为i = INT_MAX
,i++
调用未定义的行为,所以我们可以假设,i > 0
因为我们可以假设任何我们喜欢的东西.
因此i
总是> 0
可以删除测试代码.
实际上,如果C程序试图通过指针算法计算一个值而不是一个指向一个元素的指针,或一个超过同一个数组元素结尾的指针,那么它的行为就是未定义的.从C11 6.5.6/8:
如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象的最后一个元素,则评估不应产生溢出; 否则,行为未定义.
(出于本说明的目的,可以将类型对象T
的地址视为数组的第一个元素的地址T[1]
.)
澄清一下,"未定义的行为"意味着有关代码的结果未在管理该语言的标准中定义.实际结果取决于编译器的实现方式,并且可以从完全崩溃到完全崩溃以及介于两者之间的所有内容.
标准没有规定应该对指针进行任何范围检查.但就你的具体例子而言,这就是他们所说的:
当一个具有整数类型的表达式被添加到指针或从指针中减去时......如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象的最后一个元素,则评估不得产生溢出; 否则,行为未定义.如果结果指向数组对象的最后一个元素之后,则不应将其用作已计算的一元*运算符的操作数.
以上引用来自C99§6.5.6第8段(我手头的最新版本).
注意,上面也适用于非数组指针,因为在前面的子句中它说:
出于这些运算符的目的,指向不是数组元素的对象的指针与指向长度为1的数组的第一个元素的指针的行为相同,其中对象的类型为其元素类型.
因此,如果您执行指针运算,并且结果在边界内或指向对象末尾之后的结果,那么您将获得有效结果,否则您将获得未定义的行为.这种行为可能是你最终得到一个迷路指针,但它可能是另一回事.
是的,即使指针未被解除引用,它也是未定义的行为.
C只允许指针只指向一个元素超过数组边界.