C++ 有两个不同值的指针引用同一对象是未定义的行为吗?

C++ 有两个不同值的指针引用同一对象是未定义的行为吗?,c++,operating-system,undefined-behavior,memory-address,C++,Operating System,Undefined Behavior,Memory Address,注意:如果在读了这个问题后,你认为“这怎么可能发生”,那没关系。如果你想保持开放的心态,在问题之后,你可以遵循一些要点,这些要点说明了这是如何发生的,以及为什么这是有用的。请记住,这只是一个问题,而不是任何这些主题的教程。这些评论已经够吵的了,很难理解。如果您对这些主题有疑问,请将其作为问题发布在SO而不是评论中,我将不胜感激 问题:如果我在c int* c = /* allocate int (returns unique address) */; *c = 3; 由两个指针a和b引用:

注意:如果在读了这个问题后,你认为“这怎么可能发生”,那没关系。如果你想保持开放的心态,在问题之后,你可以遵循一些要点,这些要点说明了这是如何发生的,以及为什么这是有用的。请记住,这只是一个问题,而不是任何这些主题的教程。这些评论已经够吵的了,很难理解。如果您对这些主题有疑问,请将其作为问题发布在SO而不是评论中,我将不胜感激



问题:如果我在
c

int* c = /* allocate int (returns unique address) */;
*c = 3;
由两个指针
a
b
引用:

int* a = /* create pointer to (*c) */;
int* b = /* create pointer to (*c) */;
以便:

assert(a != b);  // the pointers point to a different address
assert(*b == 3);
*a = 2;
assert(*b == 2);  // but they refer to the same value
这是未定义的行为吗?如果是,C++标准的哪部分不允许这样做?如果不是,C++标准的哪些部分允许这个?

注意:指向的内存
c
通过一个返回唯一地址的内存分配函数进行分配(
new
malloc
,…)。创建这些具有不同值的指针的方法是非常特定于平台的,尽管在大多数unix系统中可以使用
mmap
完成,在windows上可以使用
VirtualAlloc
完成



背景:大多数操作系统(用户空间不在环0上的操作系统)在虚拟内存上运行其进程,并且具有从虚拟内存页到物理内存页的映射。其中一些系统(Linux/MacOS/BSDs/Unix和64位windows)提供一些系统调用(如
mmap
VirtualAlloc
),可用于将两个虚拟内存页映射到同一物理内存页。当进程执行此操作时,它基本上可以从两个不同的虚拟内存地址访问同一页的物理内存。也就是说,这两个指针将具有不同的值,但它们将访问相同的物理内存。谷歌关键词:
mmap
,虚拟内存,内存页。使用此功能获利的数据结构是“幻环缓冲区”(这是一个技术术语)和非重新分配动态大小的向量(即,当向量增长时不需要重新分配内存的向量)。谷歌提供的信息比我在这里所能提供的还要多

非常小的可能不起作用的示例(仅限unix)

我们首先在堆上分配一个int。以下请求是一个匿名的、非文件支持的虚拟内存映射。这里必须请求至少一个完整的内存页,但为了简单起见,我只请求
int
的大小(
mmap
无论如何都会分配一个完整的内存页):

现在我们需要将它映射到两个独立的内存位置,所以我们将它映射到同一个内存映射文件,两次,例如,两个相邻的内存位置。我们不会真正使用此文件,但仍需要创建并打开它:

mmap(c, sizeof(int), PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED, some_fd, 0);
mmap(c + 1, sizeof(int), PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED, some_fd, 0);
现在我们几乎完成了:

int* a = c;
int* b = c + 1;
这些显然是不同的虚拟地址:

assert(a != b);
但它们指向同一个非文件支持的物理内存页:

*a = 314;
assert(*b == 314);

好了。使用
VirtualAlloc
同样可以在Windows上完成,但API有点不同。

完全可以允许两个不同的指针指向同一对象,条件是它们的类型与 原始对象。没有什么可以阻止这一点,这当然不是未定义的行为

什么是未定义的行为是当您不尊重时,即您有两个不同类型的指针引用同一个对象。本标准第3.10/10节对此进行了说明。但你的例子并非如此

现在是你问题的难点:你能有两个不同值的指针指向同一个对象吗

  • 指针管理是实现定义的。在一些较旧的CPU架构上,编译器使用内存模型,使用和偏移寄存器。然后两者结合起来,找出它们在内存中引用的唯一地址。根据指针的存储方式,例如,如果将指针存储为段和偏移量的并置,则实际上可以有两个不同值的指针指向同一对象
  • 但是,根据相等运算符的定义(标准,第5.10/3节),指向同一对象的两个指针是相等的。无论编译器如何实现指针,这都应为真(即,即使按位值不同,如果它们引用相同的对象,则比较应返回真)

是的,可以使用多重继承,如下所示:

#include <iostream>
using namespace std;

class A { int a; };

class B { int b; };

class C : public A, public B { };

void f(A &a) { cout << &a << endl; }

void g(B &b) { cout << &b << endl; }

int main() {
    C c;
    f(c);
    g(c);
}
现在,您可以封装机制以在子类C中获得相同的共享值:

class A {
  int a;
public:
  virtual int getValue() { return a; }
  virtual void setValue(int v) { a = v; }
};

class B {
  int b;
public:
  virtual int getValue() { return b; }
  virtual void setValue(int v) { b = v; }
};

class C : public A, public B {
  int c;
public:
  virtual int getValue() { return c; }
  virtual void setValue(int v) { c = v; }
};

void f(A &a) {
  cout << &a << endl;
  cout << a.getValue() << endl;
  a.setValue(5);
  cout << a.getValue() << endl;
}

void g(B &b) {
  cout << &b << endl;
  cout << b.getValue() << endl;
}

int main() {
    C c;
    c.setValue(3);
    f(c);
    g(c);
}
看起来有两个对象(实际上是一个具有两个地址的对象),但它们共享相同的值


请注意,在ISO FAQ

< P>中,您应该仔细考虑对象的地址(Es),C++标准没有定义“代码> MMAP< /COD>”或任何其他映射内存的方法。C++标准只关心查看内存的一种方式。如果系统使用虚拟内存,那么标准只关注虚拟内存。据我所知,虚拟内存和物理内存之间没有关系

标准对内存的描述:

<>一个C++程序可用的内存由一个或多个相邻字节序列组成。每个字节都有一个唯一的地址

标准对对象的说明:

除非对象是位字段或大小为零的基类子对象,否则该对象的地址就是它占用的第一个字节的地址。如果一个对象是另一个对象的子对象,或者至少一个对象是ze的基类子对象,则两个非位字段的对象可能具有相同的地址
0x7fff5aba2878
0x7fff5aba287c
class A {
  int a;
public:
  virtual int getValue() { return a; }
  virtual void setValue(int v) { a = v; }
};

class B {
  int b;
public:
  virtual int getValue() { return b; }
  virtual void setValue(int v) { b = v; }
};

class C : public A, public B {
  int c;
public:
  virtual int getValue() { return c; }
  virtual void setValue(int v) { c = v; }
};

void f(A &a) {
  cout << &a << endl;
  cout << a.getValue() << endl;
  a.setValue(5);
  cout << a.getValue() << endl;
}

void g(B &b) {
  cout << &b << endl;
  cout << b.getValue() << endl;
}

int main() {
    C c;
    c.setValue(3);
    f(c);
    g(c);
}
0x7fff51063860
3
5
0x7fff51063870
5
int *a, *b; // initialize with magic mapping of your choice
*a = 1;
if(a != b) {
    *b = 2;
    std::cout << *a; // what is the value of *a?
}