你所不知的 OutputDebugString --- OutputDebugString 长度限制及实现依赖探讨
OutputDebugString 是很常用的调试函数, 起作用是将参数的字符串, 在运行时输出到特定的位置, 以便我们能都获知程序执行的情况. 其 MSDN: http://msdn.microsoft.com/en-us/library/windows/apps/aa363362(v=vs.85).aspx
其中比较重要的两点:
1. Applications should send very minimal debug output...
2. OutputDebugStringW converts the specified string based on the current system locale information and passes it to OutputDebugStringA to be displayed. As a result, some Unicode characters may not be displayed correctly.
首先我们来看第 1 点. msdn 说: "very mininal" 到底是多 mininal 呢? 让我们写一个程序测试一下:
1 // main.cpp 2 // 3 4 #include <iostream> 5 using namespace std; 6 7 int main() 8 { 9 const size_t len = 1 * 1024 * 1024; 10 void *p = reinterpret_cast<void *>(new char[len]); // 1 MB. 11 12 // OutputDebugStringW 测试. 13 { 14 wchar_t *pw = reinterpret_cast<wchar_t *>(p); 15 size_t lenW = len / sizeof(pw[0]); 16 fill(pw, pw + lenW - 1, L'1'); 17 18 // `wcslen(pw) == 1 * 1024 * 1024 - 1`. 19 pw[lenW - 1] = '\0'; 20 OutputDebugStringW(pw); // 太长, 无法输出. 21 22 // `wcslen(pw) == 32768`. 23 pw[327678] = '\0'; 24 OutputDebugStringW(pw); // 太长, 无法输出. 25 26 // `wcslen(pw) == 32767`. 27 pw[32767] = '\0'; 28 OutputDebugStringW(pw); // 太长, 无法输出. 29 30 // `wcslen(pw) == 32766`. 31 pw[32766] = '\0'; 32 OutputDebugStringW(pw); // ok. 正常输出. 33 } 34 35 // OutputDebugStringA 测试. 36 { 37 char *pa = reinterpret_cast<char *>(p); 38 size_t lenA = len / sizeof(pa[0]); 39 fill(pa, pa + lenA - 1, '1'); 40 41 // `strlen(pa) == 65536`. 42 pa[65535] = '\0'; 43 OutputDebugStringA(pa); // 太长, 无法输出. 44 45 pa[65534] = '\0'; 46 OutputDebugStringA(pa); // ok. 正常输出. 47 } 48 49 return 0; 50 }
通过上述实验, 我们得知:
1. W 系列: 32766. (包括结尾的 L'\0')
2. A 系列: 65534. (包括结尾的 '\0')
那么两个数字暗示着什么? 如下:
1. 32766 == 0xFFFF / 2 - 1. (红色部分, 是因为 `sizeof(wchar_t) == 2`)
2. 65534 == 0xFFFF - 1.
也就是说:
1. OutputDebugStringW 最长支持 32766 个字符的输出(包括结尾的 L'\0').
2. OutputDebugStringA 最长支持 65534 个字符的输出(包括结尾的 L'\0').
那么为什么是 32766 而不是 32767 呢? 这一点我们只能求助于 ReAct OS 了:
图中能够看出, OutputDebugStringW 是通过将其输出内容转换为 AnsiString, 然而就是这个 `RtlUnicodeStringToAnsiString` 中, 对 `Length` 重新计算了长度, 但是 `(UniSource->Lenth + sizeof(WCHAR)) / sizeof(WCHAR)` 相当于 `(65535 + 1) / 2 == 32768`, 因此导致 `32768 > MAXUSHORT` 成立, 最终函数执行失败.
再来看看第 2 点. 无论 W 还是 A 系列, 因为最终都会将输出内容转换为 ANSI, 因此很多字符是无法输出的. 比如四个龙组成的 "𪚥", 读作 zhe(2). 不举代码了, 看效果吧:
但是这个例子要注意, 由于 vs 编辑器的默认文件存储格式 ANSI 存储的, 因此第一次编译的时候应该会提醒由于当前文本格式不支持某些字符, 要求你换一个格式存储文件, 这是你选择 Unicode 就行了. 编译运行就能看到效果了.
当今我们使用的 Windows 中, 大部分函数都是 A 系列依赖于 W 系列, 也就是遇到 ANSI 字符, 会有系统 API 转换为相应的 UNICODE 版本, 然后调用 W 系列的相应函数. 但是 OutputDebugString 却是一个例外, 这也导致了即使使用 OutputDebugStringW 也会有很多字符都没法正确输出.
这再次提醒我们, 做企业级应用, 如果需要同时提供 ANSI 和 UNICODE 版本, 则应该由 W 版本实现具体功能, A 版本转换为 W 版本然后调用之. 当然, 这应该是个人尽皆知的问题才对.