进程间通信:传递 C 风格结构与 C++ 对象
Interprocess communication: passing C-style structs vs C++-objects
Warning/Disclaimer:
这个问题里面有说法,但是我在过去半个小时左右的小调查中找不到下面声明的答案。我只是好奇这里是否有人已经知道这件事。
本题无代码。只是技术问题。
背景:
我有一个遗留应用程序,它使用在进程之间传递的 C 样式结构进行 进程间通信。这工作得很好,并且已经工作了很多年,甚至在我来到这个星球之前很久:P。
我应该编写一个将成为此应用程序一部分的新流程。不知不觉中,我用 C++ 编写了它,假设我们使用的任何 IPC 都可以处理这个问题。不幸的是,后来我(从同事那里)发现现有的基础设施只能传递 C 风格的结构。
'Unverified' claims/statements:
此外,一位同事列出了以下原因,说明为什么 C++ 在这种情况下是一个糟糕的选择。
C++ 对象有虚表。 C 风格的结构只是变量和值。因此,C 风格的结构可以在进程中传递,而 C++ 对象不能。
使用 C 风格的结构,我们可以嵌入结构大小等信息,以便双方都知道期望什么和发送什么,但是对于 C++ 对象,这是不可能的,因为 'the size of the vtable could vary'.
'If we change compilers, then it is even worse. We would have even more permutations to deal with for the case of C++ objects.'
调查索赔:
不用说,这位同事对C有点偏见,但他比我更有经验,大概知道他在说什么。 我是语言不可知论者。但这立即让我开始思考。怎么用C++不能进行进程间通信呢?我用谷歌搜索,第一个点击总是来自 Whosebug,比如这个:
Inter-Process Communication Recommendation
并且我查看了此处列出的不同 IPC 方法。
https://en.wikipedia.org/wiki/Inter-process_communication#Approaches
我的意思是,我跟进了列表中的每个方法,如管道或共享内存等,每个人都不断指出的唯一警告是,指针(duh!当然)不能传递像这样,一些同步问题可能会逐渐出现 - je nachdem。
但是我找不到任何可以反驳或证实他的 'claims' 的东西。 (当然,我可以继续挖掘一天剩下的时间。:P)
问题:
他的三个说法是真的还是只是 FUD?考虑到这一点,我想要传递的那些对象中的所有内容也只有 POD 变量和一些 STL 容器,如 std::vector
和 std::pair
及其值(没有指针或任何东西),以及这些变量的吸气剂。 除了虚析构函数外没有虚函数,它的存在是因为我继承了一个基本消息class的所有消息,因为当时我想可能有一些通用的基本功能。 (我现在可以很容易地摆脱这个基数 class,因为直到现在那里还没有真正常见的东西!谢天谢地,出于某种原因,我将消息的解析和格式化保存在一个单独的 class 中。运气好或有远见?:D )
这实际上也让我想知道,编译器如何知道结构何时是 C 风格结构,因为我们对整个项目都使用 g++ 编译器?是不是使用了'virtual'关键字?
我不是在为我的案子寻求解决方案。我可以将这些对象的结果包装到结构中并通过 IPC 传递它们,或者我可以摆脱基础 class 和虚拟析构函数,如上面 'my' 点 1 中所述。
不需要 Boost 或任何 C++11 的东西或任何处理它的库。在这方面的任何建议都与手头的问题无关。
(p.s。既然我发布并重新阅读了我发布的内容,我想将可能在阅读的任何 reader 脑袋中蔓延的想法扼杀在萌芽状态这个,那个...我问这个是为了我的知识,而不是为了和那个同事争论。怀疑是好的,但如果我们都假设其他人有良好的意图,那么对社区来说会很好。:) )
- 他的三个说法是真的还是只是 FUD?考虑到这一点,我想要传递的那些对象中的所有内容也只有 POD 变量和一些 STL 容器,如 std::vector 和 std::pair 及其值(没有指针或任何东西),以及这些变量的吸气剂。除了虚析构函数之外没有虚函数,它的存在是因为我从一个基本消息 class 继承了所有消息,因为当时我在想可能有一些共同的基本功能。 (我现在可以很容易地摆脱这个基数 class,因为直到现在那里还没有真正常见的东西!谢天谢地,出于某种原因,我将消息的解析和格式化保存在一个单独的 class 中。运气好或有远见?:D)
不,一旦 stl 容器在您的结构中,您就不能像 POD 数据一样传递它们。未指定 stl 容器的实现,它们可能(并且在大多数情况下确实)包含用于内部目的的指针。
- 这实际上也让我想知道,编译器如何知道结构何时是 C 风格结构,因为我们对整个项目都使用 g++ 编译器?是使用了'virtual'关键字吗?
只要你的 struct/class 只有 POD 数据而没有虚函数,它就会被存储为 POD,但如果你的 IPC 的另一端是用另一个编译器编译的,对齐差异可能是一个问题and/or不同的编译器设置或不同的对齐指令(如#pragma pack等)。
- 我不是在为我的案子寻求解决方案。我可以将这些对象的结果包装到结构中并通过 IPC 传递它们,或者我可以摆脱基础 class 和虚拟析构函数,如上面 'my' 第 1 点所述。
将这些对象的结果包装到结构中并通过 IPC 传递它们对我来说听起来不错,就我个人而言,这就是我要做的。另一个解决方案也不错,没有上下文很难说哪个更好。
only caveat that everyone keeps on pointing out, is, that pointers (duh! of course) cannot be passed like this
指针值(以及对内存和资源的其他引用)在进程间确实没有意义。这显然是虚拟内存的结果。
另一个警告是,虽然 C 标准为结构指定了精确的(特定于平台的)内存布局,但 C++ 标准一般不保证 classes 的特定内存布局。例如,一个进程不一定与另一个进程就成员之间的填充量达成一致——即使在同一系统中也是如此。 C++ 仅保证标准布局类型的内存布局 - 并且此保证布局与 C 结构相匹配。
... and some STL containers like std::vector ... (no pointers or anything)
除了std::array
之外的所有标准容器都在内部使用指针。它们必须这样做,因为它们的大小是动态的,因此必须动态分配数据结构。此外,其中 none 是标准布局 classes。此外,一个标准库实现的 class 定义不能保证与另一个实现相匹配,并且两个进程可以使用不同的标准库——这在 Linux 上并不少见,其中一些进程可能使用 libstdc++(来自GNU)而其他人可能使用 libc++(来自 Clang)。
There are no virtual functions except the virtual destructor
换句话说:至少有一个虚函数(析构函数),因此有一个指向虚表的指针。而且也没有保证的内存布局,因为 classes 与虚函数从来不是标准布局 classes.
所以回答问题:
大部分情况下没有 FUD,尽管有些说法在技术上有点不准确:
- C++ 对象可能有虚表;并非所有人都这样做。 C 结构可以有指针,所以也不是所有的 C 结构都可以共享。一些 C++ 对象可以跨进程共享。具体来说,可以共享标准布局 classes(假设没有指针)。
- 确实无法共享具有 vtables 的对象。
- 标准布局 classes 有保证的内存布局。只要您将自己限制在标准布局 classes 上,更改编译器不是问题。如果您运气不好,尝试共享其他 classes 可能会奏效,但是当您开始混合编译器时,您可能会遇到问题。
C++ 标准定义了 class 是标准布局的确切条件。所有 C 结构定义都是 C++ 中的标准布局 classes。编译器知道这些规则。
这不是问题。
结论:您可以 将 C++ 用于 IPC,但您只能在该界面中使用标准布局 classes。这使您无法使用许多 C++ 功能,例如虚函数、访问说明符等。但不是全部:例如,您仍然可以拥有成员函数。
但是请注意,使用 C++ 功能可能会导致进程间接口仅适用于 C++。许多语言可以与 C 接口,但几乎没有任何语言可以与 C++ 接口。
此外:如果您的 "interprocess" 通信超出了系统的边界 - 即跨网络 - 即使是 C 结构或标准布局 class 也不是一个好的表示。在那种情况下你需要序列化。
'Unverified' claims/statements:
C++ objects have vtables. C-style structs are just variables and values. Therefore C-style structs can be passed around processes, while C++ objects cannot be.
这个说法部分正确,但被误导了。
并非所有 C++ 对象都有 vtable(从技术上讲,C++ 标准根本不需要 vtable,尽管它是用于支持虚函数调度的常见实现技术,因为它提供了各种优点)。
如果您查找 this SO question and various answers,您会发现有关 C++ 中聚合和 POD 类型的讨论。问题是定义在 C++ 标准之间演变(反映在对该问题的各种回答中)。在 C++11 中,POD 类型的概念发生了变化,并有效地替换为普通和标准布局类型的概念。
POD 类型(C++11 之前)和标准布局类型(C++11 及更高版本)可以在 C++ 和 C 之间互换(即从一种语言编写的代码传递到另一种语言编写的代码,因为内存布局是兼容的)。
的确,带有任何虚函数的 C++ 对象属于不能与 C 互换的对象。指针通常不能被复制,这阻止了(大部分)C++ 标准容器的使用。
With C-style structs we can embed information like size of the
struct, so that both sides know what to expect and what to send, but
for C++ objects this is not possible since 'the size of the vtable
could vary'.
这个说法是错误的,因为有些类型没有 vtable,而且类型可以在 C++ 和 C 之间互换。
If we change compilers, then it is even worse. We would have even more permutations to deal with for the case of C++ objects.
同样,只要在 C++ 代码中正确选择类型,就可以将它们与 C 互换。
这种说法 - 对于 C 与 C++ 的互操作是正确的 - 对于 C 也是正确的。C 类型的大小是在 C 中正式定义的实现,就像在 C++ 中一样。 int
、long
、float
、double
等类型不能保证在不同的编译器中具有相同的大小。有些编译器的设置会更改某些或所有基本类型的大小(例如,具有不同浮点选项的编译器,具有影响 int
是 16 位还是 32 位的设置的编译器等)。
struct
C 中的类型也可能在成员之间有填充,并且填充可能因 C 编译器而异。许多编译器都有影响填充的编译选项,这会影响 struct
类型的大小。这可能会导致 C 中相同 struct
类型的布局不兼容。
那么这里可能发生了什么?
进程间通信的设计可能假设它总是在 C 代码之间,使用相同(或兼容)的编译器构建。 IPC 机制可能非常简单:例如,一个进程在管道的指定内存位置喷射一定量的数据,接收方将在该管道的另一端接收到的数据复制到等效的数据结构中。
隐含的假设是可以通过这种方式直接复制数据。这取决于两个程序中兼容的数据类型布局。
问题在于,由于 IPC 机制是在假设兼容 C 编译器的情况下设计的,因此您现在被告知这是因为 C 优于 C++(或其他语言)。不是。它是 IPC 如何完成的人工产物。
IPC 方法可能非常有限,但您的 C++ 代码可以通过 IPC 机制发送和接收数据,只要您在 C++ 中以适当的类型(例如标准布局)打包数据代码。另一个进程是用 C 还是 C++ 编写的也无关紧要。在 C++ 中可能需要做更多的工作(例如,将来自 C++ class 的数据打包到标准布局结构中,然后将该结构发送到另一个进程——或者如果接收数据则相反),但这当然是可能的。
无论如何,您都需要使用兼容的编译器。
并且假设您不能更改进程间通信的方式(例如,设计一个进程间通信协议,而不是盲目地将数据从内存位置向下复制到另一个进程,然后接收进程将数据复制回兼容的数据结构)。如果需要的话,有一些执行 IPC 的方法可以更好地支持一系列编程语言——尽管有不同的权衡(例如,通信带宽、转换数据以便发送的代码以及接收数据和转换数据的代码)返回数据结构)。
Warning/Disclaimer:
这个问题里面有说法,但是我在过去半个小时左右的小调查中找不到下面声明的答案。我只是好奇这里是否有人已经知道这件事。
本题无代码。只是技术问题。
背景:
我有一个遗留应用程序,它使用在进程之间传递的 C 样式结构进行 进程间通信。这工作得很好,并且已经工作了很多年,甚至在我来到这个星球之前很久:P。
我应该编写一个将成为此应用程序一部分的新流程。不知不觉中,我用 C++ 编写了它,假设我们使用的任何 IPC 都可以处理这个问题。不幸的是,后来我(从同事那里)发现现有的基础设施只能传递 C 风格的结构。
'Unverified' claims/statements:
此外,一位同事列出了以下原因,说明为什么 C++ 在这种情况下是一个糟糕的选择。
C++ 对象有虚表。 C 风格的结构只是变量和值。因此,C 风格的结构可以在进程中传递,而 C++ 对象不能。
使用 C 风格的结构,我们可以嵌入结构大小等信息,以便双方都知道期望什么和发送什么,但是对于 C++ 对象,这是不可能的,因为 'the size of the vtable could vary'.
'If we change compilers, then it is even worse. We would have even more permutations to deal with for the case of C++ objects.'
调查索赔:
不用说,这位同事对C有点偏见,但他比我更有经验,大概知道他在说什么。 我是语言不可知论者。但这立即让我开始思考。怎么用C++不能进行进程间通信呢?我用谷歌搜索,第一个点击总是来自 Whosebug,比如这个:
Inter-Process Communication Recommendation
并且我查看了此处列出的不同 IPC 方法。 https://en.wikipedia.org/wiki/Inter-process_communication#Approaches
我的意思是,我跟进了列表中的每个方法,如管道或共享内存等,每个人都不断指出的唯一警告是,指针(duh!当然)不能传递像这样,一些同步问题可能会逐渐出现 - je nachdem。
但是我找不到任何可以反驳或证实他的 'claims' 的东西。 (当然,我可以继续挖掘一天剩下的时间。:P)
问题:
他的三个说法是真的还是只是 FUD?考虑到这一点,我想要传递的那些对象中的所有内容也只有 POD 变量和一些 STL 容器,如
std::vector
和std::pair
及其值(没有指针或任何东西),以及这些变量的吸气剂。 除了虚析构函数外没有虚函数,它的存在是因为我继承了一个基本消息class的所有消息,因为当时我想可能有一些通用的基本功能。 (我现在可以很容易地摆脱这个基数 class,因为直到现在那里还没有真正常见的东西!谢天谢地,出于某种原因,我将消息的解析和格式化保存在一个单独的 class 中。运气好或有远见?:D )这实际上也让我想知道,编译器如何知道结构何时是 C 风格结构,因为我们对整个项目都使用 g++ 编译器?是不是使用了'virtual'关键字?
我不是在为我的案子寻求解决方案。我可以将这些对象的结果包装到结构中并通过 IPC 传递它们,或者我可以摆脱基础 class 和虚拟析构函数,如上面 'my' 点 1 中所述。
不需要 Boost 或任何 C++11 的东西或任何处理它的库。在这方面的任何建议都与手头的问题无关。
(p.s。既然我发布并重新阅读了我发布的内容,我想将可能在阅读的任何 reader 脑袋中蔓延的想法扼杀在萌芽状态这个,那个...我问这个是为了我的知识,而不是为了和那个同事争论。怀疑是好的,但如果我们都假设其他人有良好的意图,那么对社区来说会很好。:) )
- 他的三个说法是真的还是只是 FUD?考虑到这一点,我想要传递的那些对象中的所有内容也只有 POD 变量和一些 STL 容器,如 std::vector 和 std::pair 及其值(没有指针或任何东西),以及这些变量的吸气剂。除了虚析构函数之外没有虚函数,它的存在是因为我从一个基本消息 class 继承了所有消息,因为当时我在想可能有一些共同的基本功能。 (我现在可以很容易地摆脱这个基数 class,因为直到现在那里还没有真正常见的东西!谢天谢地,出于某种原因,我将消息的解析和格式化保存在一个单独的 class 中。运气好或有远见?:D)
不,一旦 stl 容器在您的结构中,您就不能像 POD 数据一样传递它们。未指定 stl 容器的实现,它们可能(并且在大多数情况下确实)包含用于内部目的的指针。
- 这实际上也让我想知道,编译器如何知道结构何时是 C 风格结构,因为我们对整个项目都使用 g++ 编译器?是使用了'virtual'关键字吗?
只要你的 struct/class 只有 POD 数据而没有虚函数,它就会被存储为 POD,但如果你的 IPC 的另一端是用另一个编译器编译的,对齐差异可能是一个问题and/or不同的编译器设置或不同的对齐指令(如#pragma pack等)。
- 我不是在为我的案子寻求解决方案。我可以将这些对象的结果包装到结构中并通过 IPC 传递它们,或者我可以摆脱基础 class 和虚拟析构函数,如上面 'my' 第 1 点所述。
将这些对象的结果包装到结构中并通过 IPC 传递它们对我来说听起来不错,就我个人而言,这就是我要做的。另一个解决方案也不错,没有上下文很难说哪个更好。
only caveat that everyone keeps on pointing out, is, that pointers (duh! of course) cannot be passed like this
指针值(以及对内存和资源的其他引用)在进程间确实没有意义。这显然是虚拟内存的结果。
另一个警告是,虽然 C 标准为结构指定了精确的(特定于平台的)内存布局,但 C++ 标准一般不保证 classes 的特定内存布局。例如,一个进程不一定与另一个进程就成员之间的填充量达成一致——即使在同一系统中也是如此。 C++ 仅保证标准布局类型的内存布局 - 并且此保证布局与 C 结构相匹配。
... and some STL containers like std::vector ... (no pointers or anything)
除了std::array
之外的所有标准容器都在内部使用指针。它们必须这样做,因为它们的大小是动态的,因此必须动态分配数据结构。此外,其中 none 是标准布局 classes。此外,一个标准库实现的 class 定义不能保证与另一个实现相匹配,并且两个进程可以使用不同的标准库——这在 Linux 上并不少见,其中一些进程可能使用 libstdc++(来自GNU)而其他人可能使用 libc++(来自 Clang)。
There are no virtual functions except the virtual destructor
换句话说:至少有一个虚函数(析构函数),因此有一个指向虚表的指针。而且也没有保证的内存布局,因为 classes 与虚函数从来不是标准布局 classes.
所以回答问题:
大部分情况下没有 FUD,尽管有些说法在技术上有点不准确:
- C++ 对象可能有虚表;并非所有人都这样做。 C 结构可以有指针,所以也不是所有的 C 结构都可以共享。一些 C++ 对象可以跨进程共享。具体来说,可以共享标准布局 classes(假设没有指针)。
- 确实无法共享具有 vtables 的对象。
- 标准布局 classes 有保证的内存布局。只要您将自己限制在标准布局 classes 上,更改编译器不是问题。如果您运气不好,尝试共享其他 classes 可能会奏效,但是当您开始混合编译器时,您可能会遇到问题。
C++ 标准定义了 class 是标准布局的确切条件。所有 C 结构定义都是 C++ 中的标准布局 classes。编译器知道这些规则。
这不是问题。
结论:您可以 将 C++ 用于 IPC,但您只能在该界面中使用标准布局 classes。这使您无法使用许多 C++ 功能,例如虚函数、访问说明符等。但不是全部:例如,您仍然可以拥有成员函数。
但是请注意,使用 C++ 功能可能会导致进程间接口仅适用于 C++。许多语言可以与 C 接口,但几乎没有任何语言可以与 C++ 接口。
此外:如果您的 "interprocess" 通信超出了系统的边界 - 即跨网络 - 即使是 C 结构或标准布局 class 也不是一个好的表示。在那种情况下你需要序列化。
'Unverified' claims/statements:
C++ objects have vtables. C-style structs are just variables and values. Therefore C-style structs can be passed around processes, while C++ objects cannot be.
这个说法部分正确,但被误导了。
并非所有 C++ 对象都有 vtable(从技术上讲,C++ 标准根本不需要 vtable,尽管它是用于支持虚函数调度的常见实现技术,因为它提供了各种优点)。
如果您查找 this SO question and various answers,您会发现有关 C++ 中聚合和 POD 类型的讨论。问题是定义在 C++ 标准之间演变(反映在对该问题的各种回答中)。在 C++11 中,POD 类型的概念发生了变化,并有效地替换为普通和标准布局类型的概念。
POD 类型(C++11 之前)和标准布局类型(C++11 及更高版本)可以在 C++ 和 C 之间互换(即从一种语言编写的代码传递到另一种语言编写的代码,因为内存布局是兼容的)。
的确,带有任何虚函数的 C++ 对象属于不能与 C 互换的对象。指针通常不能被复制,这阻止了(大部分)C++ 标准容器的使用。
With C-style structs we can embed information like size of the struct, so that both sides know what to expect and what to send, but for C++ objects this is not possible since 'the size of the vtable could vary'.
这个说法是错误的,因为有些类型没有 vtable,而且类型可以在 C++ 和 C 之间互换。
If we change compilers, then it is even worse. We would have even more permutations to deal with for the case of C++ objects.
同样,只要在 C++ 代码中正确选择类型,就可以将它们与 C 互换。
这种说法 - 对于 C 与 C++ 的互操作是正确的 - 对于 C 也是正确的。C 类型的大小是在 C 中正式定义的实现,就像在 C++ 中一样。 int
、long
、float
、double
等类型不能保证在不同的编译器中具有相同的大小。有些编译器的设置会更改某些或所有基本类型的大小(例如,具有不同浮点选项的编译器,具有影响 int
是 16 位还是 32 位的设置的编译器等)。
struct
C 中的类型也可能在成员之间有填充,并且填充可能因 C 编译器而异。许多编译器都有影响填充的编译选项,这会影响 struct
类型的大小。这可能会导致 C 中相同 struct
类型的布局不兼容。
那么这里可能发生了什么?
进程间通信的设计可能假设它总是在 C 代码之间,使用相同(或兼容)的编译器构建。 IPC 机制可能非常简单:例如,一个进程在管道的指定内存位置喷射一定量的数据,接收方将在该管道的另一端接收到的数据复制到等效的数据结构中。
隐含的假设是可以通过这种方式直接复制数据。这取决于两个程序中兼容的数据类型布局。
问题在于,由于 IPC 机制是在假设兼容 C 编译器的情况下设计的,因此您现在被告知这是因为 C 优于 C++(或其他语言)。不是。它是 IPC 如何完成的人工产物。
IPC 方法可能非常有限,但您的 C++ 代码可以通过 IPC 机制发送和接收数据,只要您在 C++ 中以适当的类型(例如标准布局)打包数据代码。另一个进程是用 C 还是 C++ 编写的也无关紧要。在 C++ 中可能需要做更多的工作(例如,将来自 C++ class 的数据打包到标准布局结构中,然后将该结构发送到另一个进程——或者如果接收数据则相反),但这当然是可能的。
无论如何,您都需要使用兼容的编译器。
并且假设您不能更改进程间通信的方式(例如,设计一个进程间通信协议,而不是盲目地将数据从内存位置向下复制到另一个进程,然后接收进程将数据复制回兼容的数据结构)。如果需要的话,有一些执行 IPC 的方法可以更好地支持一系列编程语言——尽管有不同的权衡(例如,通信带宽、转换数据以便发送的代码以及接收数据和转换数据的代码)返回数据结构)。