指针变量只是带有某些运算符的整数还是 "symbolic"?

Are pointer variables just integers with some operators or are they "symbolic"?

编辑:原来的单词选择令人困惑。 "symbolic" 一词比原来的 ("mystical") 好多了。

在关于我之前的 C++ 问题的讨论中,有人告诉我指针是

听起来不错!如果没有什么是象征性的并且指针是它的表示,那么我可以执行以下操作。我可以吗?

#include <stdio.h>
#include <string.h>

int main() {
    int a[1] = { 0 }, *pa1 = &a[0] + 1, b = 1, *pb = &b;
    if (memcmp (&pa1, &pb, sizeof pa1) == 0) {
        printf ("pa1 == pb\n");
        *pa1 = 2;
    }
    else {
        printf ("pa1 != pb\n");
        pa1 = &a[0]; // ensure well defined behaviour in printf
    }
    printf ("b = %d *pa1 = %d\n", b, *pa1);
    return 0;
 }

这是一个 C 和 C++ 问题。

使用 GNU GCC v4.8.3 使用 Compile and Execute C Online 进行测试:gcc -O2 -Wall 给出

pa1 == pb                                                                                                                                                                                       
b = 1 *pa1 = 2    

使用 GNU GCC v4.8.3 使用 Compile and Execute C++ Online 进行测试:g++ -O2 -Wall

pa1 == pb                                                                                                                                                                                       
b = 1 *pa1 = 2        

因此 通过 (&a)[1]b 的修改在 C 和 C++ 中使用 GCC 失败

当然,我想要一个基于标准引号的答案。

编辑:为了回应 &a + 1 上对 UB 的批评,现在 a 是一个包含 1 个元素的数组。

相关:Dereferencing an out of bound pointer that contains the address of an object (array of array)

补充说明:"mystical" 一词最早由 Tony Delroy here 使用。借错了

首先要说的是,在一种编译器上生成的代码在一种体系结构上的一次测试样本并不是对语言行为得出结论的基础。

c++(和 c)是为了便于移植而创建的通用语言。也就是说,在一个系统上用 C++ 编写的结构良好的程序应该 运行 在任何其他系统上(除非调用系统特定的服务)。

曾几何时,由于包括向后兼容性和成本在内的各种原因,内存映射在所有处理器上都不是连续的。

例如,我曾经在 6809 系统上编写代码,其中一半的内存是通过在内存映射的非分页部分寻址的 PIA 分页的。我的 c 编译器能够处理这个问题,因为对于那个编译器来说,指针是一种 'mystical' 类型,它知道如何写入 PIA。

80386 系列有一种寻址模式,其中地址以 16 字节为一组进行组织。查找 FAR 个指针,您会看到不同的指针算法。

这就是c++指针的发展史。并非所有芯片制造商都是 "well behaved" 并且该语言(通常)无需重写源代码即可适应所有这些制造商。

如果您关闭优化器,代码将按预期运行。

通过使用未定义的指针算法,你在愚弄优化器。 优化器发现没有代码写入 b,因此它可以安全地将其存储在寄存器中。事实证明,您以非标准方式获取了 b 的地址,并以优化器看不到的方式修改了该值。

如果你读过C标准,它说指针可能很神秘。 gcc 指针并不神秘。它们存储在普通内存中,由构成所有其他数据类型的相同类型的字节组成。您遇到的行为是由于您的代码没有遵守为您选择的优化器级别规定的限制。

编辑:

修改后的代码仍然是UB。该标准不允许引用 a[1],即使指针值恰好与另一个指针值相同。所以优化器 允许将 b 的值存储在寄存器中。

窃取 TartanLlama 的名言:

[expr.add]/5 "[for pointer addition, ] if both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined."

因此编译器可以假定您的指针指向 a 数组,或者指向末尾的一个。如果它指向最后一个,你不能推迟它。但是像你这样肯定不可能是最后一个,所以只能在数组里面。

现在你有了代码(精简版)

b = 1;
*pa1 = 2;

其中 pa 指向数组 ab 是一个单独的变量。当您打印它们时,您会得到准确的 12,即您分配给它们的值。

优化编译器可以解决这个问题,甚至不需要将 12 存储到内存中。它可以只打印最终结果。

C 被认为是一种指针和整数密切相关的语言,具体关系取决于目标平台。指针和整数之间的关系使该语言非常适合低级或系统编程的目的。为了下面的讨论,我将这种语言称为 "Low-Level C" [LLC].

C 标准委员会写了一个不同 语言的描述,其中没有明确禁止这种关系,但没有以任何有用的方式承认,即使实现为目标和应用程序字段生成代码,其中这种关系很有用。我将这种语言称为 "High Level Only C" [HLOC].

在编写标准的日子里,大多数自称为 C 实现的东西都处理 LLC 的方言。大多数有用的编译器处理一种方言,它在比 HLOC 更多的情况下定义有用的语义,但不如 LLC 那么多。指针的行为更像整数还是更像抽象的神秘实体取决于使用的是哪种方言。如果有人在进行系统编程,那么将 C 视为将指针和整数视为密切相关是合理的,因为适合该目的的 LLC 方言可以这样做,而不适合该目的的 HLOC 方言则不适合该目的。然而,在进行高端数字运算时,人们会更经常使用不承认这种关系的 HLOC 方言。

真正的问题和这么多争论的根源在于 LLC 和 HLOC 越来越不同,但都被称为 C。