Appearance
为什么给某些Windows函数传递未对齐的Unicode字符串会失败?
原文:Why do some Windows functions fail if I pass an unaligned Unicode string?
日期:2025年6月5日
一位客户发现,如果传递了位于非偶数地址上的 Unicode 字符串(在 Windows 上,这意味着字符串是使用两个字节的数据类型 wchar_t
作为码元进行编码的 UTF-16LE 格式),那么一些(但不是全部)函数会无法接受这些字符串。为什么这个情况没有被记录在文档中呢?
这是编程的基本规则之一:除非另有明确许可,否则指针必须正确对齐。
在C和C++语言中,未对齐的指针形式被明确规定不会返回任何有用的值。
在C中:
(6.3.2.3 指针) 如果产生的指针没有为引用的类型正确对齐,则行为是未定义的。
在C++中:
[expr.static.cast](13) 如果原始指针值表示内存中字节的地址A,而A不满足T的对齐要求,则产生的指针值不明确。
因此,简单地创建一个未对齐的指针已经让你脱离了允许的(在C中)或至少有意义的(在C++中)操作的范围,所以你不应该对使用未对齐指针会导致无意义的结果感到惊讶。
至于为什么某些函数比其他函数更容易炸裂,这完全取决于这些函数如何使用指针以及谁检测到未对齐的指针。
如果你使用的是对对齐敏感的处理器,当代码试图从该指针读取数据时,你可能会失败。如果在用户模式下进行访问,您将收到违规访问异常,进程可能会崩溃。如果在内核模式下进行访问,内核模式参数验证器可能会返回一个无效的参数错误。(内核模式必须保护自己免受用户模式的影响。)
如果您使用的处理器可以容忍未对齐的数据访问,那么您可能会在一段时间内逍遥法外,直到代码对需要对齐的数据进行处理。例如,原子操作通常需要对齐的数据,即使在通常可以容忍未对齐的处理器上也是如此。
尽管x86-64通常具有对齐容错性,但仍有一些地方对对齐敏感。例如,一些涉及SIMD寄存器的指令需要对齐。SIMD寄存器通常用于复制内存块,由于wchar_t
具有2字节对齐,因此执行块复制的switch
语句在16个合法起点中只有8个,因为所有奇数地址都是无效的。如果你传递了一个奇数地址,你很可能会栽在switch
语句上并执行无意义的复制操作。
Microsoft C++编译器有一个特殊的非标准关键字__unaligned
,用于声明指针可能未对齐,这告诉编译器对该指针后面的数据的任何访问都必须使用对齐不敏感的指令。对于某些处理器来说,这可能相当昂贵。
将未对齐指针的使用限制在明确允许未对齐指针的地方。你可以通过查找Windows SDK宏UNALIGNED
来知道这些地方在哪里。例如:
c++
LWSTDAPI_(int)
SHFormatDateTimeA(
_In_ const FILETIME UNALIGNED * pft,
_Inout_opt_ DWORD * pdwFlags,
_Out_writes_(cchBuf) LPSTR pszBuf,
UINT cchBuf);