浅谈C中的wprintf和宽字符显⽰
今天在CSDN的Blog⾸页看到⼀篇⽂章“ ”,由于前⼀阵业余翻译了 ”⼀⽂,⾃⼰对字符集、编码和Unicode等内容⼀直保着者很强的兴趣,⾃然不会放过这样的⽂章。
作者的⽂章写得很明⽩易懂,虽然有⼀些概念上的细节问题我觉得有商榷之处;作者还给出⼀个简单的在windows下使⽤wprintf正确输出字符串“中⽂”的⼩例⼦,我linux下模仿作者给出的⽰例代码写了如下的⽰例代码:
#include <cstdio>
#include <cstdlib>
#include <clocale>
#include <cwchar>
int main(int argc, char* argv[])
{
wchar_t wstr[] = L"中⽂";
setlocale(LC_ALL, "zh_CN.UTF-8");
wprintf(L"%s/n",wstr);
return 0;
}
这⾥需要说明的是我的机器的locale为"zh_CN-UTF-8"
然⽽程序的运⾏结果却让我很诧异
whodare@whodare:$ ./a.out
-N
我的第⼀反应就是作者的⽰例代码是不是有问题,毕竟这⾥⾯调⽤的全都是C的标准库函数,不应该存在移植性问题;然⽽,我了台windows机器测试作者的代码,结果让我很郁闷,⼀切正常......
为 什么我在Linux下的程序就不对呢?我很不服⽓,于是开始以各种关键字进⾏搜索,想看看别⼈是否遇到过类似的问题。⼀个搜索结果引起了我的主意,有⼈说 问题出在wprintf中的格式转换符上,将%s替换成%ls就没有这样的问题。带着⼏分怀疑,我修改了上⾯的程序,编译运⾏后,居然真的就没问题了
#include <cstdio>
#include <cstdlib>
#include <clocale>
#include <cwchar>
int main(int argc, char* argv[])
...{
wchar_t wstr[] = L"中⽂";
setlocale(LC_ALL, "zh_CN.UTF-8");
wprintf(L"%s ",wstr);
wprintf(L"%ls ",wstr);
printf怎么输出字符return 0;
}
上述代码的运⾏结果
whodare@whodare:$ ./a.out
-N
中⽂
问题解决了,可我还是感到迷茫:格式转换符"ls"和“s"的区别是什么?为什么原来的程序会出问题?“-N"这个字符串是怎么冒出来的?为什么作者在windows下的程序就不存在该问题?
这么多的疑惑堵在⼼⼝,我哪能⼼安呢。知其然还要知其所以然嘛!花了⼀个下午的时间仔细读了下wprintf的manual,并在gdb的帮助下做了各种试验,终于算是把我的疑惑基本都解决了。
⼀、以下的所有试验都是以“中⽂”为例,因此有必要先把它的Unicdoe码值、UTF-8编码都列出来,以便于更好的理解下⽂
‘中’  Unicode码值:U+4E2D  UTF-8 编码 e4 b8 ad
‘⽂’  Unicode码值:U+6587  UTF-8 编码  e6 96 87
⼆、我们需要理解⽤char[ ]和wchar_t [ ]来存放“中⽂”时有什么不同
char    str[]="中⽂";
wchar_t wstr[] = L"中⽂";
我们使⽤gdb这个强⼤的⼯具来查看str[]和wst[]中究竟都存放了哪些值(请注意颜⾊之间的对应关系)
(gdb) x /8xb &str
0xbf83decd:    0xe4    0xb8    0xad    0xe6    0x96    0x870x000xf0
(gdb) x /12xb &wstr
0xbf83dec0:    0x2d    0x4e    0x00    0x000x87    0x65    0x00    0x00
0xbf83dec8:    0x00    0x00    0x00    0x00
不难看出,char str[ ]中存储的是“中⽂"的UTF-8编码,这是因为我的机器的locale是zh_CN.UTF-8,程序源⽂件的⾃然采⽤的是UTF-8编码,因此编译器 在处理 char str[ ]="中⽂"; 时,t它对str[]所做得初始化实际上可以理解成    char str[ ]={
0xe4,0xb8,0xad,0xe6,0x96,0x87,0x00}
⽽wchar_t wstr[ ]中存放的是“中⽂"的Unicode码值,这符合C标准对宽字符的定义。这⾥需要解释的是C标准中规定宽字符是16 bit的字符,⽽从GNU glibc 2.2开始,类型wchar_t只⽤于存放32-bit的ISO 10646码值(你可以粗略的把ISO 10646理解成Unicode,尽管它们并不是⼀回事),⽽独⽴于当前使⽤的locale;因此在上⾯的输出中,我们看到每个Unicode码值⽤ 32bit表⽰,⽽不是16bit。
三、关于%s和%ls的区别
我搜到了⼀篇帖⼦(很伤感,我再此发现在CS领域,最靠的住的资料总是英⽂的),⾥⾯对各种格式转换符有详细的解释,愿意看原⽂的同学直接忽略本段⽂字.......
www-ccs.ucsd.edu/c/lib_prin.html
⾸先,%ls和%s的区别很简单,%ls意味着将对应的参数会被当作基于宽字符的字符串(wide chraracter string )看待,⽽%s则意味着对应的参数会被当作普通字符串(multi-byte string)看待。
其次,不要因为上⾯⼀句话⽽错误的认为%s只⽤于printf,⽽%ls只⽤于wprintf 。实际上,(printf, wprintf) 和(%s,%ls)这两个元组之间是相互独⽴的,也就是说它们之间的四种组合都是可以的。
再次,printf⽤于byte stream,即输出流中的每个字符颤1 byte;⽽wprintf则⽤于wide stream,输出流中的每个字符不⽌ 1 byte。
说了⼀堆废话,还是结合实例来看看%ls和%s的区别吧
例⼦1 printf + %s + wstr
printf("%s ",wstr);
whodare@whodare:$ ./a.out
-N
哈,这个郁闷的"-N"⼜⼀次出现!为什么会出现呢?让我来分析⼀下printf在执⾏时所完成的操作吧。
这⾥⽤了%s, printf 就会将对应的参数wstr视为普通字符串(尽管我们清楚他是个wcs⽽不是mbs);
另⼀⽅⾯,我们已经看到了wstr[ ]的内存布局,其前3 byte为 0x2d ,0x4e,0x00。我们都知道C中的字符串以'/0'为结束标志,因此printf只会处理wstr[ ]中的前三个byte,⽽查⼀查ASCII表,0x2d对应字符'-',0x4e对应字符'N',所以我们会看到”-N"这个诡异的输出。
例⼦2 printf + %ls + wstr
printf("%ls ",wstr);
whodare@whodare:$ ./a.out
中⽂
使⽤了%ls,printf会将对应的参数视为宽字符串(wcs),⽽printf⼜对应byte stream,因此这⾥要对宽字符(wcs)进⾏转换,变成普通的字符串(mbs)。这⾥的转换是printf通过对每个宽字符隐式的调⽤wcrtom
b ()这个标准库函数完成的。按么,wcrtomb()这个函数进⾏是按照什么规则进⾏转换的?这就是setlocale()的作⽤所在了,wcrtomb 会依据程序员设定的locale,将wcha_t中存放的码值,转换为相应的的多字节编码。
回到例⼦中,我的机器的locale为zh_CN.UTF-8,对应的编码为UTF-8,因此wstr[ ]中存放的Unicode码值会转换为UTF-8编码的形式输出到标准输出流中,这样采⽤UTF-8编码的console就能正确识别受到的字节流并显⽰出"中⽂"
例⼦3  wprintf + %s +wstr (最初的代码!)
wprintf(L"%s ",wstr);
whodare@whodare:$ ./a.out
-N
使⽤了%s,wprintf会将对应的参数视为普通字符串mbs,尽管我们还是很清楚它其实是个wcs。wprintf 使⽤的是wide stream,因此需要将所给的mbs参数转换为wcs再由wprintf完成输出;这个转换是由wprintf隐式的对mbs不断调⽤mbrtowc来 完成,转换规则依然是和locale相关的。
我们知道wstr的内存布局为:
0x2d    0x4e    0x00    0x000x87    0x65    0x00    0x00
0x00    0x00    0x00    0x00
该"mbs"的转换结果为 L‘0x2d' + L '0x4e' + L '0x00' ,最终输出结果⼜是讨厌的"-N"
例⼦4 wprintf + %ls+ wstr
wprintf(L"%ls ",wstr);
whodare@whodare:$ ./a.out
中⽂
使⽤了%ls,wprintf会将对应参数视为宽字符串wcs,这次终于没有搞错。因此wprintf会顺利的将给定的宽字符串写⼊标准输出流,最终正确显⽰"中⽂"
看完这4个例⼦,你对wprintf、printf和%ls 、%s的使⽤还有疑惑么?
四、⼩结
1。要清楚%ls和%s的意义在于指明所期待的参数是何种字符串,⽽printf和wprintf的区别在于所使⽤的是不同类型的stream
2。貌似在linux下输出“中⽂"的正确⽅法是 wprintf( "%ls/n",L"中⽂") ,⽽引⽂中作者在Windows成功操作的wprintf("%s/n", L"中⽂")在linux⽆法正确⼯作,⾄于为何wprintf这个标准库函数在两个系统下有不同表现,我是⽆⼼再向下深挖了,难道这⼜是VC⼀处不符合标准的地⽅?.......
3 。貌似还有⼀个%S,单独⽤于表⽰对应参数是宽字符串
谁能告诉我该问题的答案,不盛感激.......

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。