指针的memcpy与赋值相同吗?

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;
 }
Run Code Online (Sandbox Code Playgroud)

我想基于标准报价的答案.

编辑

根据大众需求,这是我想知道的:

  • 对于给定类型的指针,指针的语义"值"(根据规范的行为)仅由其数值(它包含的数字地址)确定?
  • 如果没有,可以只复制指针中包含的物理地址,同时省略相关的语义?

这里假设有一个超过结束指针的偶然发生意外指向另一个对象; 我怎样才能使用这样一个结束指针来访问另一个对象?

除了使用另一个对象的地址副本外,我有权做任何事情.(这是了解C中指针的游戏.)

IOW,我试着像黑手党一样回收脏钱.但我通过提取其值表示来回收脏指针.然后它看起来像干净的钱,我的意思是指针.没有人可以区分,不是吗?

Ant*_*ala 6

问题是:

这个程序在所有情况下都有效吗?

答案是"不,不是".


该计划唯一有趣的部分是在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?
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,我的编译器上的保护表达式是正确的(当然,通过使这些具有静态存储持续时间,编译器无法真正证明它们在临时中没有被其他东西修改...)

指针pa1指向刚好超过数组末尾的a指针,并且是一个有效指针,但不能解除引用,即*pa1给定该值时具有未定义的行为.现在的情况是,将该值复制到p另一个将使指针有效.

答案是否定的,这仍然无效,但标准本身并没有明确说明.委员会对C标准缺陷报告DR 260的回应说:

如果两个对象具有相同的位模式表示并且它们的类型相同,则它们仍然可以比较为不相等(例如,如果一个对象具有不确定的值),并且如果一个是不确定的值,则尝试读取这样的对象会调用未定义的行为.允许实现跟踪位模式的起源,并将表示不确定值的那些与表示确定值的那些不同.他们也可以将基于不同来源的指针视为不同,即使它们是按位相同的.

即你甚至不能得出这样的结论:if pa1pb是相同类型的指针并且memcmp (&pa1, &pb, sizeof pa1) == 0确实它也是必要的pa1 == pb,更不用说将不可引用指针的位模式复制pa1到另一个对象并再次返回将pa1有效.

答复仍在继续:

请注意,使用赋值或按位复制memcpymemmove确定值使目标获取相同的确定值.

也就是说,它证实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
}
Run Code Online (Sandbox Code Playgroud)

GCC手册指出显然不合法.

  • `tmp + = sizeof(a [0]);`并将`tmp`转换为指针都是根据GCC在x86上的行为来定义的.但取消引用它并不行,因为GCC跟踪存储在`uintptr_t`类型的对象中的指针值的出处. (2认同)

Som*_*ude 5

指针只是一个无符号整数,其值是内存中某个位置的地址.覆盖指针变量的内容与覆盖正常int变量的内容没有什么不同.

所以是的,做例如memcpy (&p, &pa1, sizeof p)等同于赋值p = pa1,但可能效率较低.


让我们尝试不同的方式:

你有pa1一些指向某个对象(或者更确切地说,超出某个对象),然后你有指向&pa1变量的指针pa1(即变量pa1在内存中的位置).

从图形上看,它看起来像这样:

+------+     +-----+     +-------+
| &pa1 | --> | pa1 | --> | &a[1] |
+------+     +-----+     +-------+

[注:&a[0] + 1与[ 相同&a[1]]

  • *"指针只是一个无符号整数"*这是简化标准,还是解释实现?虽然我猜这里并不重要,但是指针在标准中有些神奇,例如6.2.4p2"当指针指向(或刚刚过去)的对象到达其生命周期的末尾时,指针的值变得不确定." (3认同)
  • @curiousguy它仍然是未定义的行为.取消引用越界导致未定义的行为,没有办法绕过它.它恰好在特定系统上使用特定编译器工作并且安全,并不会使它变得不那么简单. (2认同)

Tar*_*ama 5

不确定的行为:n部分发挥作用。

进入编译器1和编译器2,右移。

int a[1] = { 0 }, *pa1 = &a[0] + 1, b = 1, *pb = &b;
Run Code Online (Sandbox Code Playgroud)

[Compiler1]您好,apa1bpb。结识您真是太好了。现在您就坐在那里,我们将遍历代码的其余部分,以查看是否可以为您分配一些不错的堆栈空间。

编译器1浏览其余的代码,偶尔皱着眉头,并在纸上做一些标记。编译器2抬起鼻子,凝视着窗外。

[Compiler1]好吧,恐怕b我决定优化您了。我只是无法检测到某个地方修改了您的记忆。也许您的程序员使用“未定义行为”做了一些技巧来解决此问题,但是我被允许假定不存在这种UB。对不起。

退出b,被熊追赶。

[Compiler2]等等!在那里稍等片刻b。我不必为优化此代码而烦恼,所以我决定在堆栈上给您一个舒适的空间。

b 跳得高兴,但是一旦他因不确定的行为而被改变,便被鼻恶魔谋杀。

[旁白]这样就结束了可变的悲惨故事b。这个故事的寓意是,一个人永远不能依靠不确定的行为