指向共享内存 POSIX 的指针类型

Ter*_* Li 2 c c++ pointers shared-memory

我最初的问题在这里,似乎没有人感兴趣。

我决定打破这个乏味的问题并提出以下问题:

char* shared_memory;
shared_memory = (char*) shmat (segment_id, 0, 0);
Run Code Online (Sandbox Code Playgroud)

我们通常会像上面的例子那样获得指向共享内存的指针吗?换句话说,我们应该总是将指针投射到char*更适合我们需要的地方吗?

pae*_*bal 5

我不完全了解shmat,但我对 WinAPI 等效项(MapViewOfFile )有一些经验,因此我将给出更一般的答案。

因为您将两个问题联系在一起,而另一个问题是关于共享内存中的 C++ 对象,所以我将在这里处理 C 和 C++ 两种情况。我邀请您将 [c++] 标签添加到您的问题中。

内存和共享内存

无论 APIvoid *是什么,您最终都会得到一个,因为它就是这样:某个内存区域的地址(共享与否)。

那个“分配 API”(malloc、shmat、MapViewOfFile 等)不知道你会用那块内存做什么,你会使用什么抽象(C 结构、C++ 对象,甚至 C 宏或内置类型) ,所以 API 唯一能做的就是给你:

  • 一个地址,因此一个 void *
  • 对齐(通常类似于“该地址将为您能想到的所有内容对齐”)。您应该查阅 API 文档以获取该信息。在极少数情况下没有对齐保证,那么您必须只使用该内存的对齐子集(这是另一个问题)

问题是:你会用那个记忆做什么?

您无法通过 访问该内存的内容void *,因为您无法取消引用 a void *(毕竟,它是指向void...的指针)。Avoid *只包含一个地址。不多也不少。

char抽象

你会发现的第一个抽象,也是最简单的,就是char抽象。byteC 和 C++ 中没有类型,char(或unsigned char)类型填补了这个角色。因此,如果您想以字节数组的形式访问该内存,则将该内存的返回值转换为char

/* C code */
char * pc = p ; /* p being the void * pointer */

// C++ code
char * pc = static_cast<char *>(p) ; // p being the void * pointer
Run Code Online (Sandbox Code Playgroud)

struct抽象

第二个抽象是假设共享内存是一个结构体(或者甚至是一个结构体数组),因此您应该将指针转换为指向该结构体的指针:

/* C code */
typedef struct S { /* etc. */ } S ;
S * ps = p ; /* p being the void * pointer */

// C++ code
struct S { /* etc. */ } ;
S * ps = static_cast<S *>(p) ; // p being the void * pointer
Run Code Online (Sandbox Code Playgroud)

因此,您可以通过结构访问该共享内存。

并发?

最坏的情况:两个进程将同时工作。因此,如果您不使用同步原语(如进程间互斥锁),您将陷入竞争条件(一个进程写入一个值,而另一个进程正在读取它,可能会导致读取数据损坏)

关于共享内存和指针

通常,共享内存用于进程间共享。通常,这意味着 API 可以(并且可能)为每个进程为相同的共享内存返回不同的地址。

原因有点复杂(应该是他们自己的问题),但这里是:具有指向同一内存的指针的两个进程在其各自的指针上不会具有相同的地址。

/* C code */

/* process A */
void * p = getSharedMemory("ABCD") ;
/* p could have the value 0x1234 */

/* process B */
void * p = getSharedMemory("ABCD") ;
/* p could have the value 0x56789 */
Run Code Online (Sandbox Code Playgroud)

地址在共享内存中是无用的。如果您将来自进程 A 的有效指针地址放入共享内存,则该指针地址在进程 B 中将无效。因此,永远不要放入地址。

您可以放入共享内存的是索引。例如,您可以拥有以下结构:

/* C code */
typedef struct S
{
   size_t index ;
   double value[1000] ;
} S ;
Run Code Online (Sandbox Code Playgroud)

使用此结构,您可以在value数组中的索引 42 处设置值 3.1415 :

/* C code - process A */
S * s = p ; /* p being the pointer to the shared memory */
s->index = 42 ;
s->value[42] = 3.1415 ;
Run Code Online (Sandbox Code Playgroud)

然后在另一个进程中检索它:

/* C code - process B */
S * s = p ; /* p being the pointer to the shared memory */
size_t index = s->index ;
double value = s->value[index] ;
Run Code Online (Sandbox Code Playgroud)

关于 C 和 C++

这种共享内存是 API 问题,而不是 C 或 C++ 特定问题。

在您的原始问题中,您提到了共享内存中的 C++ 对象,因此我将详细说明 C 和 C++ 中的一些差异,尽管这些差异超出了您问题的真实范围。

C 类型转换与 C++ 类型转换

在 C 中,将任何void *指针隐式转换为任何类型的指针都是合法的T *。在 C++ 中,你需要一个静态转换:

/* valid C code */
T * t = p ;        /* p being a void * pointer */
T * t = (T *) p ;  /* useless but authorized cast */

// valid C++ code ;
T * t = static_cast<T *>(p) ; // p being a void * pointer
T * t = (T *) p ;             // considered bad style for multiple reasons
Run Code Online (Sandbox Code Playgroud)

所以通常,为了生成 C/C++ 兼容的代码,大多数人会使用这两种语言通用的 C 风格类型转换,总是会招致语言律师的评论(我对此感到内疚)。

尽管争论激烈,但事实是每种语言都是正确的,因为尽管它们有很强的相似性和共同点,但它们在一个主要领域有所不同:C 是一种弱类型语言,而 C++ 是一种强类型语言。

将 C++ 对象放入共享内存

还记得我写的不应该在共享内存中放置指针的部分吗?

这对于 C 和 C++ 是正确的:当您在共享内存中拥有一个指针而不是相对索引时,您就有了一个可能的问题(即一个可能的错误)。

因此,如果在共享内存中放入一个结构体,该结构体的指针成员包含进程 A 中的地址,则该地址在进程 B 中将无效。

C++ 对象提供了强大的抽象,这意味着它们易于使用且安全(例如,使用时std::stringstd::vector<std::string> objects尽管涉及内存分配量,但没有内存泄漏风险)。但是这种强大的抽象只是隐藏了这样一个事实,即在内部,您仍然有指针......

共享内存中 C++ 对象的第二个难点是必须手动处理构造和析构(使用放置新函数和显式析构函数调用)。

结论:除非您知道您使用的对象可以处理它,并且您正确使用了该对象,否则请编写以下强制转换:

// C++ code
struct MyObject { /* constructors, destructors, etc. */ }  ;

MyObject * myObject = static_cast<MyObject*>(p) ; // p being void *
Run Code Online (Sandbox Code Playgroud)

使用指向共享内存的指针将无法正常工作。