Shi*_*sui 35 c pointers heap-memory memory-layout undefined-behavior
在 K&R(C 编程语言第 2 版)第 5 章中,我阅读了以下内容:
首先,在某些情况下可以比较指针。如果
p和q指向同一个数组的成员,则关系一样==,!=,<,>=,等正常工作。
这似乎意味着只能比较指向同一数组的指针。
但是,当我尝试此代码时
char t = 't';
char *pt = &t;
char x = 'x';
char *px = &x;
printf("%d\n", pt > px);
Run Code Online (Sandbox Code Playgroud)
1 被打印到屏幕上。
首先,我认为我会得到 undefined 或某种类型或错误,因为pt和px没有指向同一个数组(至少在我的理解中)。
也是pt > px因为两个指针都指向存储在栈上的变量,栈向下增长,所以内存地址t大于x?为什么pt > px是真的?
当 malloc 被引入时,我变得更加困惑。 同样在第 8.7 章的 K&R 中写到以下内容:
然而,仍然存在一种假设,即
sbrk可以有意义地比较指向由 返回的不同块的指针。仅允许在数组内进行指针比较的标准并不能保证这一点。因此,这个版本的malloc仅在一般指针比较有意义的机器之间是可移植的。
将指向堆上 malloced 空间的指针与指向堆栈变量的指针进行比较,我没有任何问题。
例如,下面的代码工作正常,1并被打印:
char t = 't';
char *pt = &t;
char *px = malloc(10);
strcpy(px, pt);
printf("%d\n", pt > px);
Run Code Online (Sandbox Code Playgroud)
根据我对编译器的实验,我认为任何指针都可以与任何其他指针进行比较,而不管它们分别指向何处。此外,我认为两个指针之间的指针算术很好,无论它们分别指向哪里,因为算术只是使用指针存储的内存地址。
尽管如此,我对我在 K&R 中阅读的内容感到困惑。
我问的原因是因为我的教授。实际上使它成为一个考试问题。他给出了以下代码:
Run Code Online (Sandbox Code Playgroud)struct A { char *p0; char *p1; }; int main(int argc, char **argv) { char a = 0; char *b = "W"; char c[] = [ 'L', 'O', 'L', 0 ]; struct A p[3]; p[0].p0 = &a; p[1].p0 = b; p[2].p0 = c; for(int i = 0; i < 3; i++) { p[i].p1 = malloc(10); strcpy(p[i].p1, p[i].p0); } }这些评估是什么:
p[0].p0 < p[0].p1p[1].p0 < p[1].p1p[2].p0 < p[2].p1
答案是0,1和0。
(我的教授确实在考试中包含免责声明,即这些问题是针对 Ubuntu Linux 16.04、64 位版本编程环境的)
(编者注:如果 SO 允许更多标签,那么最后一部分将保证x86-64、linux和可能的assembly。如果问题/类的重点是特定的低级操作系统实现细节,而不是可移植的 C。)
dbu*_*ush 34
根据C11 标准,关系运算符<、<=、>和>=只能用于指向同一数组或结构对象的元素的指针。这在第 6.5.8p5 节中有详细说明:
比较两个指针时,结果取决于所指向对象在地址空间中的相对位置。如果指向对象类型的两个指针都指向同一个对象,或者都指向同一个数组对象的最后一个元素之后的一个,则它们比较相等。如果指向的对象是同一个聚合对象的成员,则指向后面声明的结构成员的指针比较大于指向结构中前面声明的成员的指针,指向下标值较大的数组元素的指针比较大于指向同一数组元素的指针具有较低的下标值。指向同一联合对象成员的所有指针比较相等。
请注意,任何不满足此要求的比较都会调用未定义的行为,这意味着(除其他外)您不能依赖结果是可重复的。
在您的特定情况下,对于两个局部变量的地址之间以及本地地址和动态地址之间的比较,该操作似乎“有效”,但是通过对您的代码进行看似无关的更改,结果可能会发生变化甚至用不同的优化设置编译相同的代码。对于未定义的行为,仅仅因为代码可能崩溃或生成错误并不意味着它会。
例如,在 8086 实模式下运行的 x86 处理器具有使用 16 位段和 16 位偏移量来构建 20 位地址的分段内存模型。因此,在这种情况下,地址不会完全转换为整数。
然而,相等运算符==和!=没有这个限制。它们可以在任何两个指向兼容类型的指针或 NULL 指针之间使用。因此==,!=在您的两个示例中使用或将生成有效的 C 代码。
但是,即使使用==和 ,!=您也可以获得一些意想不到但仍然定义明确的结果。请参阅无关指针的相等比较能否评估为真?有关这方面的更多详细信息。
关于你的教授给出的考试问题,它做出了一些有缺陷的假设:
如果您要在不满足这些假设的体系结构和/或编译器上运行此代码,那么您可能会得到非常不同的结果。
此外,这两个示例在调用 时也表现出未定义的行为strcpy,因为右操作数(在某些情况下)指向单个字符而不是空终止字符串,导致函数读取超出给定变量的边界。
ζ--*_*ζ-- 12
比较指向相同类型的两个不同数组的指针的主要问题是数组本身不需要放置在特定的相对位置——一个可能在另一个之前和之后结束。
首先,我认为我会得到 undefined 或某种类型或错误,因为 pt 和 px 没有指向同一个数组(至少在我的理解中)。
不,结果取决于实施和其他不可预测的因素。
也是pt>px,因为两个指针都指向存储在栈上的变量,栈向下增长,所以t的内存地址大于x的内存地址?这就是为什么 pt>px 是真的?
不一定有 stack。当它存在时,它不需要向下生长。它可以长大。它可能以某种奇怪的方式不连续。
此外,我认为两个指针之间的指针算术很好,无论它们分别指向哪里,因为算术只是使用指针存储的内存地址。
让我们看看第 85 页上的C 规范§6.5.8,它讨论了关系运算符(即您正在使用的比较运算符)。请注意,这不适用于直接!=或==比较。
比较两个指针时,结果取决于所指向对象在地址空间中的相对位置。... 如果指向的对象是同一聚合对象的成员, ... 指向具有较大下标值的数组元素的指针比较大于指向具有较低下标值的同一数组元素的指针。
在所有其他情况下,行为是未定义的。
最后一句话很重要。虽然我减少了一些不相关的情况以节省空间,但有一种情况对我们很重要:两个数组,不是同一结构/聚合对象1 的一部分,我们正在比较指向这两个数组的指针。这是未定义的行为。
虽然您的编译器只是插入了某种 CMP(比较)机器指令来对指针进行数字比较,但您在这里很幸运,但 UB 是一个非常危险的野兽。实际上任何事情都可能发生——您的编译器可以优化整个函数,包括可见的副作用。它可能会产生鼻妖。
1 可以比较指向属于同一结构的两个不同数组的指针,因为这属于两个数组属于同一聚合对象(结构)的子句。
然后问什么
Run Code Online (Sandbox Code Playgroud)p[0].p0 < p[0].p1 p[1].p0 < p[1].p1 p[2].p0 < p[2].p1评价为。答案是 0、1 和 0。
这些问题简化为:
而这三个问题的答案都是“实现定义”。你教授的问题是假的;他们以传统的 unix 布局为基础:
<empty>
text
rodata
rwdata
bss
< empty, used for heap >
...
stack
kernel
Run Code Online (Sandbox Code Playgroud)
但是一些现代的 unice(和替代系统)不符合这些传统。除非他们以“截至 1992 年”开头;确保在 eval 上给出 -1。
| 归档时间: |
|
| 查看次数: |
5731 次 |
| 最近记录: |