结构填充可靠性
Structure padding reliability
在 C 和 C++ 中,我们被教导将结构填充视为特定于编译器的,因此我们避免在序列化等事情上依赖它。
另一方面,每当我们 link 到第 3 方动态库或共享对象时,我们都依赖于一致的结构填充。
让我们以 <windows.h>
为例:
typedef struct _SECURITY_ATTRIBUTES {
DWORD nLength;
LPVOID lpSecurityDescriptor;
BOOL bInheritHandle;
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
HANDLE WINAPI CreateThread(
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ SIZE_T dwStackSize,
_In_ LPTHREAD_START_ROUTINE lpStartAddress,
_In_opt_ LPVOID lpParameter,
_In_ DWORD dwCreationFlags,
_Out_opt_ LPDWORD lpThreadId
);
我们不应该关心使用哪个编译器来创建 Kernel32.dll,但是编译器定义的结构填充意味着如果使用了不同的编译器,那么 Kernel32.dll 可能会取消引用 _SECURITY_ATTRIBUTES
指针与我们的应用程序打包它的方式不同。
- 我能解释一下为什么可以将结构指针传递给第三方 DLL 而不知道它们的结构填充吗?
- 对于动态加载的 DLLs/SOs(使用
LoadLibrary
或 dlopen
)是否同样适用?
- 如果我们使用
#pragma pack
,所有这些担忧都会消失吗?
- 如果是这样,
#pragma pack
是结构序列化的可靠解决方案吗?
从语言的角度来看,你说得很对。结构的填充和布局是一个实现细节,编译器可以自由做出自己的选择。
但是在实践中,要使编译器在任何特定平台上都有用,它必须遵守平台 ABI。如果有人试图生产不遵守平台 ABI 的编译器,那将毫无用处。
因此,您可以放心地假设您使用的任何编译器都与平台 ABI 兼容。你不会找到任何不存在的可行编译器。
在 C 和 C++ 中,我们被教导将结构填充视为特定于编译器的,因此我们避免在序列化等事情上依赖它。
另一方面,每当我们 link 到第 3 方动态库或共享对象时,我们都依赖于一致的结构填充。
让我们以 <windows.h>
为例:
typedef struct _SECURITY_ATTRIBUTES {
DWORD nLength;
LPVOID lpSecurityDescriptor;
BOOL bInheritHandle;
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
HANDLE WINAPI CreateThread(
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ SIZE_T dwStackSize,
_In_ LPTHREAD_START_ROUTINE lpStartAddress,
_In_opt_ LPVOID lpParameter,
_In_ DWORD dwCreationFlags,
_Out_opt_ LPDWORD lpThreadId
);
我们不应该关心使用哪个编译器来创建 Kernel32.dll,但是编译器定义的结构填充意味着如果使用了不同的编译器,那么 Kernel32.dll 可能会取消引用 _SECURITY_ATTRIBUTES
指针与我们的应用程序打包它的方式不同。
- 我能解释一下为什么可以将结构指针传递给第三方 DLL 而不知道它们的结构填充吗?
- 对于动态加载的 DLLs/SOs(使用
LoadLibrary
或dlopen
)是否同样适用? - 如果我们使用
#pragma pack
,所有这些担忧都会消失吗? - 如果是这样,
#pragma pack
是结构序列化的可靠解决方案吗?
从语言的角度来看,你说得很对。结构的填充和布局是一个实现细节,编译器可以自由做出自己的选择。
但是在实践中,要使编译器在任何特定平台上都有用,它必须遵守平台 ABI。如果有人试图生产不遵守平台 ABI 的编译器,那将毫无用处。
因此,您可以放心地假设您使用的任何编译器都与平台 ABI 兼容。你不会找到任何不存在的可行编译器。