cur*_*guy -5 c pointers memcpy language-lawyer
简介:这个问题是我收集的C和C++(以及C/C++常见子集)问题的一部分,这些问题涉及允许具有严格相同的字节表示的指针对象具有不同的"值",即行为不同对于某些操作(包括在一个对象上定义了行为,在另一个对象上定义了未定义的行为).
在另一个引起很多混淆的问题之后,这里有关于指针语义的问题,希望能够解决问题:
这个程序在所有情况下都有效吗?唯一有趣的部分是在"pa1 == pb"分支中.
#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) {
        int *p;
        printf ("pa1 == pb\n"); // interesting part
        memcpy (&p, &pa1, sizeof p); // make a copy of the representation
        memcpy (&pa1, &p, sizeof p); // pa1 is a copy of the bytes of pa1 now
        // and the bytes of pa1 happens to be the bytes of pb 
        *pa1 = 2; // does pa1 legally point to b?
    }
    else {
        printf ("pa1 != pb\n"); // failed experiment, nothing to see
        pa1 = &a[0]; // ensure well defined behavior in printf
    }
    printf ("b = %d *pa1 = %d\n", b, *pa1);
    return 0;
 }
我想基于标准报价的答案.
编辑
根据大众需求,这是我想知道的:
这里假设有一个超过结束指针的偶然发生意外指向另一个对象; 我怎样才能使用这样一个结束指针来访问另一个对象?
除了使用另一个对象的地址副本外,我有权做任何事情.(这是了解C中指针的游戏.)
IOW,我试着像黑手党一样回收脏钱.但我通过提取其值表示来回收脏指针.然后它看起来像干净的钱,我的意思是指针.没有人可以区分,不是吗?
问题是:
这个程序在所有情况下都有效吗?
答案是"不,不是".
该计划唯一有趣的部分是在if声明保护的区块内发生的事情.保证控制表达式的真实性有点困难,所以我通过将变量移动到全局范围来对其进行了一些修改.同样的问题仍然存在:这个程序是否始终有效:
#include <stdio.h>
#include <string.h>
static int a[1] = { 2 };
static int b = 1;
static int *pa1 = &a[0] + 1;
static int *pb = &b;
int main(void) {
    if (memcmp (&pa1, &pb, sizeof pa1) == 0) {
        int *p;
        printf ("pa1 == pb\n"); // interesting part
        memcpy (&p, &pa1, sizeof p); // make a copy of the representation
        memcpy (&pa1, &p, sizeof p); // pa1 is a copy of the bytes of pa1 now
        // and the bytes of pa1 happens to be the bytes of pb 
        *pa1 = 2; // does pa1 legally point to b?
    }
}
现在,我的编译器上的保护表达式是正确的(当然,通过使这些具有静态存储持续时间,编译器无法真正证明它们在临时中没有被其他东西修改...)
指针pa1指向刚好超过数组末尾的a指针,并且是一个有效指针,但不能解除引用,即*pa1给定该值时具有未定义的行为.现在的情况是,将该值复制到p另一个将使指针有效.
答案是否定的,这仍然无效,但标准本身并没有明确说明.委员会对C标准缺陷报告DR 260的回应说:
如果两个对象具有相同的位模式表示并且它们的类型相同,则它们仍然可以比较为不相等(例如,如果一个对象具有不确定的值),并且如果一个是不确定的值,则尝试读取这样的对象会调用未定义的行为.允许实现跟踪位模式的起源,并将表示不确定值的那些与表示确定值的那些不同.他们也可以将基于不同来源的指针视为不同,即使它们是按位相同的.
即你甚至不能得出这样的结论:if pa1和pb是相同类型的指针并且memcmp (&pa1, &pb, sizeof pa1) == 0确实它也是必要的pa1 == pb,更不用说将不可引用指针的位模式复制pa1到另一个对象并再次返回将pa1有效.
答复仍在继续:
请注意,使用赋值或按位复制
memcpy或memmove确定值使目标获取相同的确定值.
也就是说,它证实memcpy (&p, &pa1, sizeof p);会导致p获得相同的值pa1,它以前是没有的.
这不仅仅是一个理论问题 - 众所周知编译器会跟踪指针的来源.例如,GCC手册指出了这一点
当从指针转换为整数并再次返回时,结果指针必须引用与原始指针相同的对象,否则行为是未定义的.也就是说,可能不会使用整数运算来避免指针运算的未定义行为,如C99和C11 6.5.6/8中所禁止的那样.
即程序是否写成:
int a[1] = { 0 }, *pa1 = &a[0] + 1, b = 1, *pb = &b;
if (memcmp (&pa1, &pb, sizeof pa1) == 0) {
    uintptr_t tmp = (uintptr_t)&a[0]; // pointer to a[0]
    tmp += sizeof (a[0]); // value of address to a[1]
    pa1 = (int *)tmp;
    *pa1 = 2; // pa1 still would have the bit pattern of pb,
              // hold a valid pointer just past the end of array a,
              // but not legally point to pb
}
GCC手册指出这显然不合法.
指针只是一个无符号整数,其值是内存中某个位置的地址.覆盖指针变量的内容与覆盖正常int变量的内容没有什么不同.
所以是的,做例如memcpy (&p, &pa1, sizeof p)等同于赋值p = pa1,但可能效率较低.
让我们尝试不同的方式:
你有pa1一些指向某个对象(或者更确切地说,超出某个对象),然后你有指向&pa1变量的指针pa1(即变量pa1在内存中的位置).
从图形上看,它看起来像这样:
+------+ +-----+ +-------+ | &pa1 | --> | pa1 | --> | &a[1] | +------+ +-----+ +-------+
[注:&a[0] + 1与[ 相同&a[1]]
不确定的行为:n部分发挥作用。
进入编译器1和编译器2,右移。
int a[1] = { 0 }, *pa1 = &a[0] + 1, b = 1, *pb = &b;
[Compiler1]您好,
a,pa1,b,pb。结识您真是太好了。现在您就坐在那里,我们将遍历代码的其余部分,以查看是否可以为您分配一些不错的堆栈空间。
编译器1浏览其余的代码,偶尔皱着眉头,并在纸上做一些标记。编译器2抬起鼻子,凝视着窗外。
[Compiler1]好吧,恐怕
b我决定优化您了。我只是无法检测到某个地方修改了您的记忆。也许您的程序员使用“未定义行为”做了一些技巧来解决此问题,但是我被允许假定不存在这种UB。对不起。
退出b,被熊追赶。
[Compiler2]等等!在那里稍等片刻
b。我不必为优化此代码而烦恼,所以我决定在堆栈上给您一个舒适的空间。
b 跳得高兴,但是一旦他因不确定的行为而被改变,便被鼻恶魔谋杀。
[旁白]这样就结束了可变的悲惨故事
b。这个故事的寓意是,一个人永远不能依靠不确定的行为。