指针的memcpy与赋值相同吗?

在另一个引起很多混淆的问题之后,这里有关于指针语义的问题,希望能够解决问题:

这个程序在所有情况下都有效吗? 唯一有趣的部分是在“pa1 == pb”分支中。

#include  #include  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中指针的游戏。)

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

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

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


让我们尝试不同的方式:

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

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

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

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

未定义的行为: n部分的游戏。

Compiler1和Compiler2进入,右侧。

 int a[1] = { 0 }, *pa1 = &a[0] + 1, b = 1, *pb = &b; 

[Compiler1]你好, apa1bpb 。 和你相识真是太好了。 现在你就坐在那里,我们将查看剩下的代码,看看我们是否可以为你分配一些不错的堆栈空间。

编译器1查看其余代码,偶尔皱眉并在纸上做一些标记。 编译器2抠鼻子,盯着窗外看。

[Compiler1]好吧,我害怕, b ,我决定优化你。 我根本无法找到修改你记忆的地方。 也许你的程序员用Undefined Behavior做了一些技巧来解决这个问题,但我可以假设没有这样的UB存在。 对不起。

出口b ,由熊追逐。

[Compiler2]等等! 等一下, b 。 我无法优化这段代码,所以我决定在堆栈上给你一个舒适的空间。

b高兴地跳起来,但是一旦他被未定义的行为修改,就会被鼻子恶魔谋杀。

[叙述者]因此结束了变量b的悲伤,悲伤的故事。 这个故事的寓意是, 人们永远不能依赖于未定义的行为

在C99之前,预期实现的行为就好像任何类型的每个变量的值都存储了一系列unsigned char值; 如果检查了相同类型的两个变量的基础表示并发现它们是相等的,那么这意味着除非已经发生了未定义的行为,否则它们的值通常是相等且可互换的。 在一些地方有一点点含糊不清,例如给定

 char *p,*q; p = malloc(1); free(p); q = malloc(1); if (!memcmp(&p, &q, sizeof p)) p[0] = 1; 

每个版本的C都清楚地表明q可能或不等于p ,如果q不等于p代码,则应该期望在写入p[0]时可能发生任何事情。 虽然C89标准没有明确说明,如果对p的写入等于写入q则实现可能只有p比特等于q ,这种行为通常会被完全封装在序列中的变量模型所暗示。 unsigned char值。

C99添加了许多情况,其中变量可以按比例相等但不相等。 例如,考虑一下:

 extern int doSomething(char *p1, char *p2); int act1(char * restrict p1, char * restrict p2) { return doSomething(p1,p2); } int act2(char * restrict p) { return doSomething(p,p); } int x[4]; int act3a(void) { return act1(x,x); } int act3b(void) { return act2(x); } int act3c(void) { return doSomething(x,x); } 

调用act3aact3bact3c将导致使用两个比较等于x指针调用doSomething() ,但是如果通过act3a调用,则必须使用x专门访问doSomething写入的任何x元素,仅使用p1 ,或专门使用p2 。 如果通过act3b调用,该方法可以自由地使用p1写入元素并通过p2访问它们,反之亦然。 如果通过act3c访问,该方法可以互换使用p1p2xp1p2的二进制表示中没有任何内容表明它们是否可以与x互换使用,但是允许编译器在act1act1act1扩展doSomething ,并且这些扩展的行为根据指针访问的不同而不同。允许和禁止。

  *pa1 = 2; // does pa1 legally point to b? 

不,那个pa1指向b纯粹是巧合。 注意,程序必须在编译时符合,指针恰好在运行时具有相同的值并不重要。

没有人可以区分,不是吗?

编译器优化器可以分辨出来! 编译器优化器可以看到(通过代码的静态分析) b并且永远不会通过“合法”指针访问,因此它假设将b保存在寄存器中是安全的。 这个决定是在汇编时做出的。

底线:

“合法”指针是通过赋值或通过复制内存从合法指针获得的指针。 如果结果指针位于分配/复制的数组/内存块的合法范围内,您还可以使用指针算法获得“合法”指针。 如果指针算术的结果恰好指向另一个存储块中的有效地址,则使用这样的指针仍然是UB。

另请注意,仅当两个指针指向同一个数组/内存块时,指针比较才有效。

编辑:

哪里出错了?

该标准规定访问数组越界会导致未定义的行为。 你用一个指针取一个越界的地址,复制它然后解除引用它。

该标准规定越界指针可以比较指向另一个碰巧在内存中相邻的对象的指针(6.5.9 pt 6)。 然而,即使它们比较相等,但在语义上它们并不指向同一个对象。

在你的情况下,你不比较指针,你比较他们的位模式。 无所谓。 指针pa1仍然被认为是指向一个超过数组末尾的指针。

请注意,如果用自己编写的某个函数替换memcpy ,编译器将不知道pa1具有什么值,但它仍然可以静态地确定它不能包含“合法”获得的&b副本。

因此,在这种情况下,允许编译器优化器优化b的读/存储。

对于给定类型的指针,指针的语义“值”(根据规范的行为)仅由其数值(它包含的数字地址)确定?

不可以。标准推断有效指针只能通过使用address-of运算符( & ),通过复制另一个有效指针或通过in /减少数组边界内的指针来获取对象。 作为一种特殊情况,超过数组末尾的指针是有效的,但不能取消引用它们。 这似乎有点严格,但没有它,优化的可能性将是有限的。

如果没有,可以只复制指针中包含的物理地址,同时省略相关的语义?

不,至少不是以可移植到任何平台的方式。 在许多实现中,指针值只是地址。 语义在生成的代码中。

您已经certificate它似乎适用于特定的实现。 这并不意味着它一般都有效 。 事实上,这是一种未定义的行为,其中一种可能的结果恰好“似乎有效”。

如果,我们回到MS-DOS时代,我们有近指针(相对于特定的段)和远指针(包含段和偏移)。

大型数组通常在它们自己的段中分配,只有偏移量被用作指针。 编译器已经知道哪个段包含特定的数组,因此它可以将指针与适当的段寄存器组合在一起。

在这种情况下,你可以有两个具有相同位模式的指针,其中一个指针指向一个数组段( pa ),另一个指针指向堆栈段( pb )。 指针比较相等,但仍指向不同的东西。

更糟糕的是,具有段:偏移对的远指针可以形成有重叠段,使得不同的位模式仍然指向相同的物理存储器地址。 例如0100:02100120:0010地址相同。

C和C ++语言的设计使其可以工作。 这就是为什么我们有规则比较指针只在同一个数组中工作(给出一个总顺序),并且指针可能不指向同一个东西,即使它们包含相同的位模式。