C++字符串常量跨平台编译问题
C++字符串常量跨平台编译问题(与字符串编码相关),有需要的朋友可以参考下。
1. 问题 在C++代码中,给一个string类型的变量赋值一个中文字符串常量,例如: string s = "中文字符串" 变量s中保存的字节内容是什么?如果源文件的编码格式转换了,比如从GB2312转换为UTF-8,变量s中的内容会发生变化吗?其结果是否与编译器有关?
假设有一个C++源程序:
#include <iostream>
#include <string>
#include <iomanip>
using namespace std;
int main(int argc, char * argv[])
{
string s="中文字符串";
cout << "Length of string: "<< s.size()<< endl;
cout << "Bytes in string: "<< endl;
for (string::size_type i=0; i< s.size();i++)
{
cout <<setfill('0')<< setw(2)<<hex<< (int)(0x0ff &s[i]) << " ";
}
cout << endl;
return 0;
}
使用三种不同的编译器编译: Windows平台(简体中文): Vs2008 SP1 Linux x86(Ubuntu 10.04 LTS): G++ 4.4.3 ARM Linux: arm-none-linux-gnueabi-g++ 4.3.2(From Sourcery G++ Lite 2008q3-72)
2. 实验1 在Vs2008中创建一个空的VC++项目,并创建一个cpp文件,将上面的代码放进去,编译运行,得到的结果是: Length of string: 10 Bytes in string: d6 d0 ce c4 d7 d6 b7 fb b4 ae
在Linux下使用g++编译: g++ -o testtest.cpp,运行结果相同
使用ARM Linux的交叉编译工具: arm-none-linux-gnueabi-g++ -o testtest.cpp编译,并在目标板上运行,结果也相同。
在简体中文的Windows中运行的Vs2008,创建的C++源文件的缺省编码方式为CP936,即GB2312,该编码下,每个中文字符对应2个字节。通过检查(可以使用Python把那几个字节来换转换一下编码),可以确定,输出的10个字节就是这5个中文字符的CP936的编码值。
考虑到源代码的编码格式也是CP936,似乎可以得到如下结论,在这几个编译器中,字符串常量的字节内容,直接就是字符串在源代码文件中对应的字节内容。
照这个结论,如果把源文件转换为UTF-8编码存储,对应输出的字节应该是这几个中文字符的UTF-8编码,而长度应该是15(这几个中文字符的UTF-8编码都是3个字节)。
3. 实验2
在Vs2008中,打开源代码,选择文件菜单中的“高级保存选项",修改编码为Unicode (UTF-8带签名)-代码页65001,把同样的源代码分别在三个编译器中编译运行。 Vs2008:结果仍然与原来相同,即长度为10,字节内容为CP936的10个字节,而不是UTF-8的15个字节; G++(x86): 长度变为15,字节内容为15个字节,经验证为那几个中文字符对应的UTF-8编码; G++ ARM: 编译错误, error: stray '/357' in program error: stray '/273' in program error: stray '/277' in program 从实验的情况来看,x86下的GCC确实是按照实验1中的结论工作,字符串常量的值随文件编码改变而发生了相应的改变;而Vs2008输出的结果却表明,Vs2008似乎自动将编码转换成了原来的样子即CP936的形式,这个结论还是挺意外的。 而G++ ARM的编译错误,应该是由于这个版本的编译器不能识别源文件插入的BOM导致的,后来使用更新的交叉编译器(G++ 4.4.1 from Sourcery G++ lite2010q1-202)证实了这一点,并且输出结果与x86中的G++编译的完全相同。
到这一步,可以初步得出结论,G++不会试图转换常量的字符串编码,会直接使用与源文件字符编码对应的字符串常量。Vs2008会试图将Unicode的编码格式转换成对应地区(Locale)的缺省编码(简体中文系统下,为GB2312即代码页CP936),并按照这个编码的内容来确定常量字符串的值(后者有一些推论在里面)。
4. 跨平台编译的解决方案
如果一份代码需要在Visual Studio下编译,又需要使用GCC编译,如何选择文件的编码方式比较合理呢。 如果代码中不会出现中文或其它非Ascii字符,这当然不是问题,因为AsciiUTF-8及CP936这些编码格式在ASCII字符的范围内都是一致的。
如果有中文这类字符串常量,就会有点麻烦。如果全部采用UTF-8(带BOM),这样,在Windows下可以正常工作,而且类似于printf或cout这样的控制台输出,都可以正常输出中文字符串,因为如前面的实验,VisualStudio在编译时已经把UTF-8编码的源文件中的字符串常量编程了本地的编码,因此在操作系统的控制台窗口中输出完全没有问题。相反,如果直接向控制台输出一个UTF-8编码的中文字符串,显示是不正确的。 而在Linux下(以Ubuntu为例),由于GCC直接使用了UTF-8源代码文件中字符串的编码,而Ubuntu下,输出的UTF-8编码字符串可以被正确显示,而如果直接输出CP936编码的字符串就不行了。 因此,如果可以把所有的源代码全部转换为带UTF-8(带BOM)的编码存储,VS和GCC编译的结果,都可以得到正确的显示输出。
不过,如果要把字符串通过网络传输给另一个程序;或直接把字符串写到文件中去,这种方式下,传输的内容是不同的。在Windows下,传输的是CP936的编码内容,根据前面的例子“中文字符串"这五个汉字共传输10个字节;而用GCC编译的结果却传输15字节的UTF-8编码字符串。接收数据的程序,则需要来源的不同根据情况进行解码。如果接收程序是使用Java编写的,由于Java里面的字符串都使用Unicode,因此需要根据不同的来源,使用CP936或UTF-8来解码收到的内容为Unicode字符串。通常,客户端程序是不应该关心提供数据的服务程序是VS编译的还是GCC编译的,因此这样分别进行解码比较困难。
因此,更常见的做法是传输时使用统一的编码格式,而程序在从传输信道获得字符串或向传输信道写入字符串时,根据自身的情况进行解码或编码。而在UTF-8和CP936之间,选择更恰当的一种作为信道中传输的字符串,不是一个困难的选择。UTF-8可以传输所有的字符,而CP936只能传输简体中文的字符,从通用的角度考虑肯定应该是UTF-8。
也就是说,如果代码都使用UTF-8编码,而信道中传输字符串时也使用UTF-8编码的情况下,在VS中编译时,需要在输出字符串到信道或从信道获得字符串时,要进行CP936(或其它多字节编码)与UTF-8之间的转换。转换可以通过Windows提供的SDK完成,具体方法是,先将多字节字符串转换为Windows的WideChar的字符串,再将WideChar转换为UTF-8字符串(没有直接转换的API),反之类似。 相关的Windows的SDK为: MultiByteToWideChar WideCharToMultiByte 后者可以将WideChar转换为CP936或UTF-8.不过函数调用时,需要注意转换后的UTF-8所需要的存储长度要大于输入的WideChar字符串的长度,具体长多少,是不确定的,与字符串内容有关。有两个办法,一个是直接分配足够大的长度用来存储编码后的结果,例如WideChar字符串长度的四倍;另一个方法是一次一次试,先分配与WideChar相同的长度,调用WideChartToMultiByte,如果返回值是0且GetLastError返回 ERROR_INSUFFICIENT_BUFF,就在把缓冲区扩大点再试。
为了方便转换,写了一个Python脚本,可以用来转换一个目录内所有的.h,.cpp,.hpp,.cxx,.c文件;同时,可以识别已经转换过的文件,和有BOM标记的(UTF-16,UTF-32文件);如果没有标记,则认为是CP936编码的文件,将执行转换,脚本文件可在这里下载。 使用方法为: python toutf8.py -d youSourceDir
StackOverflow上的这篇文章同样解释了如何在C++代码里实现UTF8常量字符串的问题。
C++字符串常量跨平台编译问题的更多相关文章
- 字符数组,字符指针,字符串常量,以及sizeof的一些总结
1.以字符串形式出现的,编译器都会为该字符串自动添加一个\0作为结尾 如在代码中写"abc",编译器帮你存储的是"abc\0". 2.数组的类型是由该数组所存放 ...
- Java中的字符串常量池
ava中字符串对象创建有两种形式,一种为字面量形式,如String str = "droid";,另一种就是使用new这种标准的构造对象的方法,如String str = new ...
- C++常量(C++数值常量、字符串常量、符号常量)
http://see.xidian.edu.cn/cpp/biancheng/view/104.html 字符串常量 用双撇号括起来的部分就是字符串常量,如"abc"," ...
- 浅谈JAVA中字符串常量的储存位置
在讲述这些之前我们需要一些预备知识: java中的内存被分成以下部分: 1.栈区:由编译器自动分配释放,具体方法执行结束后,系统自动释放JVM内存资源. 其作用有保存局部变量的值,包括:1.用来保存基 ...
- Java中String字符串常量池总结
最近到广州某建站互联网公司面试,当时面试官问假设有两个字符串String a="abc",String b = "abc";问输出a==b是true还是fals ...
- Java中字符串相加和字符串常量相加区别
有一道这样的程序: public class TestStringDemo { public static void main(String[] args) { String s1 = "P ...
- Java中String字符串常量池
首先看一个例子,通过这个例子更能快速理解String常量池 public static void main(String[] args) { String a = "ab"; St ...
- Groovy 反射字符串常量方法
Keywords: Groovy, Reflection, 反射 The Reflection of Groovy String constant style method. Groovy支持以下的方 ...
- Java中用字符串常量赋值和使用new构造String对象的区别
String str1 = "ABC"; String str2 = new String("ABC"); String str1 = “ABC”;可能创建一个 ...
随机推荐
- Docker 外部访问容器Pp、数据管理volume、网络network 介绍
Docker 外部访问容器Pp.数据管理volume.网络network 介绍 外部访问容器 容器中可以运行一些网络应用,要让外部也可以访问这些应用,可以通过 -P 或 -p 参数来 指定端口映射. ...
- perl 转置矩阵
这里提供一个转置矩阵的perl脚本,R语言中的t()函数同样也能实现转置 1 use strict; 2 3 open A,"$ARGV[0]"; 4 5 my %ha; 6 my ...
- perl中tr的用法(转载)
转载:http://blog.sina.com.cn/s/blog_4a0824490101hncz.html (1)/c表示把匹配不上的字符进行替换. $temp="AAAABCDEF&q ...
- 48-Merge Sorted Array
$88. Merge Sorted Array My Submissions QuestionEditorial Solution Total Accepted: 98885 Total Submis ...
- EXCEL excel中运用ctrl+D、ctrl+enter、ctrl+E批量填充数据
在excel中,利用鼠标拖动可以快速向下或者向右填充序列或者复制单元格.但是利用快捷键也可以实现多种填充方式,本文就为大家介绍一些ctrl系列快捷键的填充,一起来看看吧. 封面tu 一,ctrl+D/ ...
- 备忘录:关于.net程序连接Oracle数据库
目录 关于使用MSSM访问Oracle数据库 关于. net 程序中连接Oracle数据库 志铭-2021年12月7日 21:22:15 关于使用MSSM访问Oracle数据库 安装访问接口组件:Or ...
- 零基础学习java------day5------do....while循环、嵌套、方法(函数)
1 do...while循环 格式 初始化语句; do { 循环体语句; 控制条件语句; }while(判断条件语句); 流程: 先执行初始化语句 再执行循环体语句 再执行条件控制语句 再做条件的判 ...
- E面波导和H面波导的问题
我感觉与窄壁平行是E面,反之为H面.通常E面(窄面)是指与电场方向平行的方向图切面(窄面):H面(宽面)是指与磁场方向平行的方向图切面(宽面).E面的意思是... ElevationH面的意思是... ...
- NSURLSession实现文件上传
7.1 涉及知识点(1)实现文件上传的方法 /* 第一个参数:请求对象 第二个参数:请求体(要上传的文件数据) block回调: NSData:响应体 NSURLResponse:响应头 NSErro ...
- 【Linux】【Shell】【Basic】字符串操作
1. 字符串切片: ${var:offset:number} 取字符串的子串: 取字符趾的最右侧的几个字符:${ ...