shmget 如何分配内存?无法使用线性寻址访问(地址边界错误)

Posted

技术标签:

【中文标题】shmget 如何分配内存?无法使用线性寻址访问(地址边界错误)【英文标题】:How does shmget allocate memory? Cannot access with linear addressing (Address Boundary error) 【发布时间】:2020-01-16 07:21:35 【问题描述】:

在以下示例中,我尝试使用shmget一个整数和10 个foo 结构分配内存,并尝试线性访问它们。但是,它会出现“地址边界错误”。

对于 MacOS 系统(但在 Linux 上应该相同),我尝试分配两个数据结构应该占用的确切内存量,并尝试对它们进行线性字节寻址。

#include <stdio.h>
#include <sys/shm.h>

typedef struct 
  int f1;
  int f2;
 foo;

int main() 
  // Size of memory. Consider, int size to be 4. (4 + 8 * 10 = 84 bytes)
  const int shmsz = sizeof(int) + sizeof(foo) * 10; 

  // Create shared mem id.
  const int shmid = shmget(IPC_PRIVATE, shmsz, 0666);
  if (shmid < 0) 
    perror("shmget failed.");
    return 1;
  

  // Pointer to the shared memory. Cast as char* for byte addressing.
  char* shmemPtr = (char*) shmat(shmid, 0, 0);
  if (*shmemPtr == -1) 
    perror("shmat failed.");
    return 1;
  

  // The integer should point to the first 4 bytes. This is the same as
  // the address pointed to by shmemPtr itself.
  int* iPtr = (int*) shmemPtr[0];

  // The 80 bytes for the 10 foos range from index 4 -> 83
  foo* fPtr = (foo*) ((void*) shmemPtr[sizeof(int)]);

  printf("i: %p\n", iPtr); // why is this 0x0 ?
  printf("x: %p\n", fPtr); // why is this 0x0 ?

  *iPtr = 0; // <-- This dereference crashes, probably since the address of iPtr is 0x0

在分配内存并通过shmat 接收指向它的指针后,我尝试创建的任何指向分配内存的指针都是0x0,并且任何取消引用都会(正确地)使程序崩溃。我希望 int*foo* 是指向共享内存的有效指针。

我只是用 C/C++ 涉足一些系统的东西,所以如果我在这里遗漏了任何东西,请原谅我。

【问题讨论】:

这是错误的:int* iPtr = (int*) shmemPtr[0];shmemPtr 是指向 char 的指针,所以 shmemPtr[0]char。您不应该将 char 转换为指针。您可能需要int *iPtr = (int *) shmemPtr;int *iPtr = (int *) &amp;shmemPtr[0];。你的编译器应该已经警告你了。如果没有,请将-Wall 添加到您的编译器开关中。 同样,foo* fPtr = (foo*) ((void*) shmemPtr[sizeof(int)]); 应该是 foo *fPtr = (foo *) &amp;shmemPtr[sizeof(int)]; 这是错误的:*shmemPtr == -1。如果shmat 失败,它会返回一个在其“值”中为-1 的指针。它不指向包含 -1 的内存位置。与shmemPtr == (char *) -1比较。 @EricPostpischil 完成了这项工作。我认为这与我没有正确引用指针有关。感谢您提供有关编译器标志的提示! 用你正在编译的语言标记这样的问题,而不是 C 和 C++。你用的是哪个? 【参考方案1】:
char* shmemPtr = (char*) shmat(shmid, 0, 0);

你现在有一个指向你的共享内存块的指针。它是一个char *,指向块的开始。到目前为止,一切顺利。

int* iPtr = (int*) shmemPtr[0];

由于shmemPtrchar *shmemPtr[0] 是它的第一个字节。以上,莫名其妙地将单个字节转换为int *。当然,这毫无意义。

您显然打算将shmemPtr 重新解释为指向您的int 的指针,因此您可以通过*iPtr 来读取您希望位于共享内存块中的第一个int。如果是这样,应该是:

int* iPtr = (int*) shmemPtr;

这是针对 C 的。如果您使用的是 C++,您还可以帮助您的编译器帮助您免于自找麻烦:

int* iPtr = reinterpret_cast<int *>(shmemPtr);

砥砺前行,我们来到:

foo* fPtr = (foo*) ((void*) shmemPtr[sizeof(int)]);

恭喜。 shmemPtr[sizeof(int)] 显然是shmemPtr[4],或共享内存块中的第五个字节;你只是拖着你的编译器,踢着和尖叫着,先把那个单字节转换成void *,然后再转换成foo *

显然,您的意思是计算那个字节的地址,然后重新解释它。

foo* fPtr = (foo *)(shmemPtr + sizeof(int));   /* C default */

foo* fPtr = reinterpret_cast<foo *>(shmemPtr + sizeof(int)); /* Preferred in C++ */

由于特定于平台的对齐问题,这仍然可能是也可能不是 100% 正确。但这将是一个好的开始。

【讨论】:

谢谢。如果它不会总是像这样对齐,那么如何以便携的方式做我正在尝试的事情?我是否为每个组件单独创建共享内存? C++ 标签刚刚被删除——这样做是有道理的(没有特定于 C++ 的代码)。但是,不幸的是,这使得reinterpret_cast 不合适。对于是否恢复 C++ 标签或建议您从答案中删除仅限 C++ 的建议,我有两种看法。你有偏好吗? 这里可以介绍 C 和 C++。就对齐而言,在这样的简单情况下,性能不是问题,在计算地址后,通常的方法是简单地将memcpy 转换为单独声明的struct @SamVarshavchik:过去我不得不寻找特定于 C 或 C++ 的答案。也许您认为您可以通过提供两者/任何一个的答案来提供帮助,但是混乱的搜索结果是有代价的。不得不查看太多答案以丢弃不适用的答案可能会很麻烦。

以上是关于shmget 如何分配内存?无法使用线性寻址访问(地址边界错误)的主要内容,如果未能解决你的问题,请参考以下文章

实模式

数组介绍

线性表--数组(Array)

Linux 共享内存:shmget() 与 mmap()?

共享内存段与共享内存对象

第二个 shmget 总是返回拒绝访问