c - 指针算术通过先前的成员地址(在同一个结构中)导致指向另一个结构成员的指针
问题描述
C标准对指针算术结果的看法是什么?通过同一结构中的前一个成员地址指向另一个结构成员?
代码 1(无结构),mystery_1
int mystery_1(void)
{
int one = 1, two = 2;
int *p1 = &one + 1;
int *p2 = &two;
unsigned long i1 = (unsigned long) p1;
unsigned long i2 = (unsigned long) p2;
if (i1 == i2)
return p1 == p2;
return 2;
}
从代码1,我知道结果是不确定的,因为无法保证堆栈上的局部变量是如何放置的。
如果我使用这样的结构(代码 2)怎么办?
代码 2(带结构),mystery_2
int mystery_2(void)
{
struct { int one, two; } my_var = {
.one = 1, .two = 2
};
int *p1 = &my_var.one + 1;
int *p2 = &my_var.two;
unsigned long i1 = (unsigned long) p1;
unsigned long i2 = (unsigned long) p2;
if (i1 == i2)
return p1 == p2;
return 2;
}
编译器输出
神螺栓链接: https ://godbolt.org/z/jGoKfETn7
海合会 10.2
mystery_1:
xorl %eax, %eax # return 0, while clang returns 2 (fine as no guarantee)
ret
mystery_2:
movl $1, %eax # return 1, as compiler must consider the memory order of struct members
ret
铿锵声 11.0.1
mystery_1: # @mystery_1
movl $2, %eax # return 2, while gcc returns 0 (fine as no guarantee)
retq
mystery_2: # @mystery_2
movl $1, %eax # return 1, as compiler must consider the memory order of struct members
retq
我的理解
- 在代码1中,返回值是不确定的,因为堆栈上的局部变量的内存布局没有保证。
- 在代码 2 中,返回值被确定并明确定义为
1
asp1 == p2
产生 true,因为 struct 保证了内存布局。所以下一个地址my_var.one
ismy_var.two
,并且编译器不允许假设p1
andp2
是不同的,因为它们的出处。
问题
- 我的理解正确吗?
- 根据 C 标准,
mystery_2
总是返回 1 为p1 == p2
真吗? - 在
mystery_2
中,编译器是否允许假设p1 != p2
,所以函数返回 0?
问题
我与某人讨论了 struct case ( mystery_2
),他们说:
p1
指向(过去)一,并p2
指向二。在 C 规范中,这些被视为不同的“对象”。然后规范继续定义指向不同对象的指针可能比较不同,即使两个指针具有完全相同的位模式
解决方案
根据 C 2018 6.5.6 8,指针算术的两个基础是:
- 可以调整指向数组元素的指针(通过整数的加减法)以指向数组的任何元素或指向末尾(最后一个元素之后的一个)。C 标准未定义的算术以外的算术。
- 对于指针算术,单个对象的行为就像一个对象的数组。
因此int *p1 = &one + 1;
具有定义的行为。
关于:
unsigned long i1 = (unsigned long) p1;
unsigned long i2 = (unsigned long) p2;
由于它不是这个问题的重点,我们假设实现定义的指针到 an 的转换unsigned long
会产生一个唯一值,该值唯一地标识指针值。(也就是说,将任何地址转换为unsigned long
仅会为该地址生成一个值,并且将该值转换回指针会重现该地址。C 标准不保证这一点。)
那么,如果i1 == i2
,则意味着p1 == p2
,反之亦然。根据 C 2018 6.5.9 6,p1
并且p2
只有当two
(p2
指向)已在内存中布局超过one
(p1
指向刚刚超出)时,才能比较相等。(一般来说,由于其他原因,指针可以比较相等,但这些情况涉及指向同一个对象、结构及其第一个成员、同一个函数等的指针,所有这些都被排除在这个特殊的p1
and之外p2
。)
因此,如果代码 1 中的代码two
紧随其后在内存中布局,则返回 1,否则返回one
2。
在代码 2 中也是如此。&my_var.one + 1
定义了指针算术,并且结果p1
比较等于p2
当且仅当成员two
紧跟one
在内存中的成员之后。
不过,two
也不必马上跟进one
。这个说法是不正确的:
... struct 保证内存布局。
C 标准允许实现在结构成员之间放置填充。常见的 C 实现不会这样做,struct { int one, two; }
因为它不需要对齐(一旦one
对齐,紧随其后的地址也适合对齐int
,因此不需要填充),但 C 标准不保证这一点。
笔记
uintptr_t
,在 中声明<stdint.h>
,是将指针转换为整数的更好选择。但是,标准只保证(uintptr_t) px == (uintptr_t) py
暗示px == py
,不保证px == py
暗示(uintptr_t) px == (uintptr_t) py
。换句话说,将两个指向同一个对象的指针转换为uintptr_t
可能会产生两个不同的值,尽管将它们转换回指针会导致指针比较相等。
推荐阅读
- php - 为什么 in_array 以 Object::Class 作为键返回 false?
- shiny - 我可以在 Shiny 中使用输出服务器作为新的输入参数吗?
- java - 我想使用 LayoutParams 让 ViewFlipper MATCH_PARENT 但失败
- google-chrome - 有没有这样的东西:Cr-os=genericimg?
- reactjs - 如何在 React-Konva 中获取行的结束位置
- dictionary - Dart 中定义的 Map [] 运算符在哪里?
- java - Hive:将两张地图合并为一列
- arrays - 如何将数组从一个结构中的字符串数组传递到类 Swift 中的按钮数组中的标题
- postgresql - How to sort Alphanumeric using Postgresql (specific data)?
- python - 我怎样才能让这两个脚本一起工作?