有符号溢出是未定义的。无符号溢出定义为模算术。
所以我的问题是,以下是定义的还是未定义的:
#include <assert.h>
#include <cstdint>
struct X { int x;/* ... anything ... */ };
X array[3] = { 2, 3, 4 /* or other init values that is compatible with X */};
X* element = array + 1;
std::uintptr_t plus1 = 1;
std::uintptr_t minus1 = 0-plus1;
int main()
{
printf("%p\n%p\n", element + plus1, array + 2);
printf("\n");
printf("%p\n%p\n", element + minus1, array);
assert(element + plus1 == array + 2);
assert(element + minus1 == array);
}
虽然我声明plus1
/minus1
,但我实际上是指任何 /-值。如果我理解正确,这应该可以工作。我说得对吗?
std::uintptr_t
是一个无符号整数
std::intptr_t
是一个有符号整数
因此定义了std::uintpr_t
的溢出,而std::intptr_t
的溢出导致UB。
minus1
是std::uintptr_t
可以容纳的最大数字。
从http://en.cppreference.com/w/cpp/language/operator_arithmetic
元素minss1
与元素-1
不同元素minus1
超出了数组的有效范围,因此导致UB。
指针算术只有在所涉及的指针保持在单个数组对象中或刚刚超过数组的末尾时才被很好地定义。所以从技术上讲,表达式元素minus1
是未定义的行为——因为minus1
是一个非常大的值,它运行在数组的末尾。
现在在实践中,这很可能是可行的,但在技术上还没有定义。
指针算法是在抽象机器中定义的。
在抽象机器中,ptr x
仅当ptr
存在于一个对象中,且其地址位于边的-x
范围内时才有效。
这个抽象机器不关心指针或有符号或无符号整数的具体大小。在这个抽象机器中,有符号和无符号整数的值都是实整数或未指定的值。
minus1
具有32位uintptr_t
等于0xffffffff,一个大的正整数。
element
是否指向一个足够大的对象,以至于0xffffffff*sizeof(X)之后它仍在该对象内?不,没有。
所以元素minus1
是一个未定义的操作。任何事情都可能发生。
在您的硬件上,将指针算法简单地插入机器代码可能会导致它被包围。但依赖这一点并不安全。
一方面,优化器有时喜欢证明事物。如果他们证明元素
大于数组
的地址,那么对元素
的无符号添加不可能使其等于数组
。因此编译器可以将元素无符号值==数组
优化为false
。
如果您更改优化设置,升级编译器,或者完全无害的事情,如更改内联的位置,或其他内联的代码,或链接时优化的启发式,或月亮的相位变化,就会发生这种优化。
依赖它工作是危险的,即使它真的工作了,因为你现在负责审计的不是源代码,而是它生成的机器代码。
概念 在我们了解了指针的基本概念之后,接下来我们来看一下指针和C中给我们提供的关键字中的类型修饰符它们之间的关系。指针就是存储地址的一种特殊变量。那这个变量跟我们的修饰符之间还有哪些更深层次的符号限制呐?我们在实际开发过程中,经常会运用修饰符,把指针的这种限制范围更明确的告诉给用户,通过不同修饰符告诉给用户指针的具体属性。 指针变量也是一个变量,它可以改变存储的地址,指向一个地方再指向另外一个地方
C语言允许函数的返回值是一个 指针(地址),我们将这样的函数称为 指针函数。下面的例子定义了一个函数 strlong(),用来返回两个字符串中较长的一个: 运行结果: C Language↙ c.biancheng.net↙ Longer string: c.biancheng.net 用指针作为函数返回值时需要注意的一点是,函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和
C 语言的指针常常被人们认为是 C 语言中的灵魂所在,可以完成很多高难度的操作。但是更多的人感觉 C 语言的指针的存在如同噩梦一般,因为稍不注意,就会引起灾难性的后果。 不管你是不是喜欢 C 语言中的指针,我们都要学习这种看起来很神奇的东西。因为指针这个概念不是 C 语言首创的,而是苏联的计算机科学家首创的。 1. 什么是指针? 指针是什么?这是来自灵魂的拷问。其实指针也是一种变量。我们之前也说过
指针逻辑运算符操作 指针变量存储的是一个地址,可以将它理解成一个数值。只是编译器会因为它是一个指针类型而将它读取作为一个地址。逻辑运算符包括>=,<=,==,!=;显然我们比较两个地址的数值的大小是没有意义的。 指针中最常用的逻辑操作符==,!=这两个是最常用的。 我们将0x0规定为地址的无效值,结束标志。NULL就是一个在C编译器中一个被定义好了的宏,它代表了这个无效地址。我们经常使用p==NU
多级指针概述 我们将指针变量理解为一个存储地址的变量。如果这个地址里面存储的依然是地址,那么我们就可以定义一个二维指针。通过一个二维指针,我们通过两次寻址操作就可以找到这个值。 比如我们定义一个int **p;这是一个二维指针,当编译器看到p的时候,它知道这是一个指针变量,指向一个地址;当它看到第二个的时候,它知道指向的地址里面依然还是地址。如下图所示: 如果我们将p[0]和p[1]分别指向两个字
我们已经在内存中看到了int类型的存储方式,如果我们存储的是0x12,就会在内存中得到二进制的编码0001 0010。如果我们存储一个float类型的1.2,又在内存中又有怎样的存储方式呐?下面我们使用两种方法来读取存储在内存中的float变量。 用int类型的指针来读取同样大小的四个字节 测试代码如下 我们可以看到由于不同的指针类型赋值,依然有一个警告。 运行结果如下 这就是浮点数1.2在内存中