【问题标题】:Portable C Implementation of Relative Pointers相对指针的可移植 C 实现
【发布时间】:2021-12-03 00:09:41
【问题描述】:

我将相对指针定义为Ginger Bill describes as Self-Relative Pointers

... 将 [偏移量将应用到的] 定义为偏移量本身的内存地址

例如,考虑这个结构:

struct house {
  int32_t weight;
}
struct person {
  int32_t age;
  struct house* residence;
}
int32_t getPersonsHousesWeight(struct person* p) {
  return p->residence->weight;
}

我认为可能可行的 C 语言中相同事物的相对指针实现是:

struct house { ... } // same as before
struct person {
  int32_t age;
  int64_t residence; // an offset from the person's address in memory
}
int32_t getPersonsHousesWeight(struct person* p) {
  return ((struct residence*)((char*)p + (p->residence)))->weight;
}

假设一切都对齐良好(全部 8 个字节),这是否没有未定义的行为?

编辑

@tstanisl 提供了一个很好的答案(我已经接受了),它在堆栈分配的上下文中彻底解释了 UB。我很好奇分配到一大块连续堆中会如何影响这个分析。例如:

int foo(void) {
  char* base = mmap(NULL,4096,PROT_WRITE | PROT_READ,-1,MAP_PRIVATE | MAP_ANONYMOUS);
  // Omitting mmap error checking
  struct person* myPerson = (struct person*)(base + 128);
  struct house* myHouse = (struct house*)(base + 256);
  int32_t delta = (char*)myHouse - (char*)myPerson;
  // Does the computation of delta invoke UB?
}

【问题讨论】:

  • 对我来说,这取决于你如何定义偏移量,如果你在运行时设置它像p->residence = (intptr_t)p - (intptr_t)h(假设person* phouse* h)那么它会很好用。
  • 这取决于pstruct house 对象是否属于同一个大对象,例如struct person_in_da_house { struct person p; struct house h; }。另外,指针p是如何构造的还有一些技术条件。

标签: c


【解决方案1】:

通常它将是 UB。 第一种情况是personhouse 属于单独的对象。 在这种情况下,它将是 UB,因为指针算术是在对象之外执行的。

int foo(void) {
  struct person p;
  struct house h;
  p.residence = (char*)&h - (char*)&p; // already UB
  getPersonsHousesWeight(&p); // UB again
}

实际上,这意味着编译器没有义务注意到从&p 构造的指针访问的对象可以与对象h 别名,因为ph 是单独的内存区域(又名对象)。

当两个对象都放在一个更大的对象中时,情况会好一些。虽然它仍然是技术 UB。

int foo(void) {
  struct ph {
    struct person p;
    struct house h;
  } ph;
  ph.p.residence = (char*)&ph.h - (char*)&ph.p; // still UB
  getPersonsHousesWeight(&ph.p); // UB again
}

它是 UB,因为指针运算是在成员对象之外完成的。 (char*)&ph.h - 1ph.h 之外的指针。

请注意,此代码几乎可以在任何地方使用。 否则,大量使用类似container_of 的宏将无法破坏包括 Linux 内核在内的大量现有代码。

为避免 UB,必须以特殊方式构造指针以避免移动到原始对象之外。 而不是使用&ph.h 应该使用(char*)&ph + offsetof(struct ph, h)。 同样&ph.p 应替换为(char*)&ph + offsetof(struct ph, p)

现在这段代码应该是可移植的:

int foo(void) {
  struct ph {
    struct person p;
    struct house h;
  } ph;
  struct person *p_ptr = (struct person*)((char*)&ph + offsetof(struct ph, p));
  struct house  *h_ptr = (struct house*) ((char*)&ph + offsetof(struct ph, h));
  ph.p.residence = (char*)h_ptr - (char*)p_ptr;
  getPersonsHousesWeight(p_ptr);
}

虽然它非常晦涩。 关于这个话题的有趣讨论可以在link

找到

【讨论】:

  • 谢谢!这是非常彻底的。我在原始问题的末尾添加了一个编辑,以更具体地询问堆分配的情况。在堆分配的上下文中,我不太清楚什么是“原始对象”。是操作系统交给用户的整个内存块吗?或者切片成块会以某种方式改变原始指针?
  • @AndrewThaddeusMartin C 标准与操作系统、堆和堆栈无关。为了完全可移植,您应该将 malloc 返回的每个指针视为放置在与其他所有内容完全分离的唯一地址空间中。我猜相对指针只能用于在同一次 malloc() 调用返回的区域内创建的对象。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多