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”;可能创建一个 ...
随机推荐
- Codeforces 985G - Team Players(三元环)
Codeforces 题目传送门 & 洛谷题目传送门 真·ycx 做啥题我就做啥题 考虑枚举 \(j\),我们预处理出 \(c1_i\) 表示与 \(i\) 相连的编号 \(<i\) 的 ...
- ACAM 题乱做
之前做了不少 ACAM,不过没怎么整理起来,还是有点可惜的. 打 * 的是推荐一做的题目. I. *CF1437G Death DBMS 见 我的题解. II. *CF1202E You Are Gi ...
- dart系列之:数学什么的就是小意思,看我dart如何玩转它
目录 简介 dart:math包的构成 math Random 总结 简介 dart也可以进行数学运算,dart为数学爱好者专门创建了一个dart:math包来处理数学方面的各种操作.dart:mat ...
- CMake 工程调用 Makefile 编译项目
本文主要介绍如何将一个依赖 Makefile 项目(MIDG)移植到 CMake 上. 首先介绍项目文件结构,文件主要由三个目录组成 3rdParty include src 其中,3rdParty ...
- Pyquery解析库的安装和使用
Pyquery同样是一个强大的网页解析工具,它提供了和jQuery类似的语法来解析HTML文档,支持CSS选择器,使用非常方便.GitHub:https://github.com/gawel/pyqu ...
- SpringCloud微服务实战——搭建企业级开发框架(三十):整合EasyExcel实现数据表格导入导出功能
批量上传数据导入.数据统计分析导出,已经基本是系统必不可缺的一项功能,这里从性能和易用性方面考虑,集成EasyExcel.EasyExcel是一个基于Java的简单.省内存的读写Excel的开源项 ...
- 学习java第十九天
一.今日收获 1.java完全学习手册第三章算法的3.2排序,比较了跟c语言排序上的不同 2.观看哔哩哔哩上的教学视频 二.今日问题 1.快速排序法的运行调试多次 2.哔哩哔哩教学视频的一些术语不太理 ...
- E面波导和H面波导的问题
我感觉与窄壁平行是E面,反之为H面.通常E面(窄面)是指与电场方向平行的方向图切面(窄面):H面(宽面)是指与磁场方向平行的方向图切面(宽面).E面的意思是... ElevationH面的意思是... ...
- webservice--常用注解
定义说明书的显示方法1.@WebService(serviceName="PojoService", portName="PojoPort", name=&qu ...
- Linux基础命令---htdigest建立和更新apache服务器摘要
htdigest htdigest指令用来建立和更新apache服务器用于摘要认证的存放用户认证信息的文件. 此命令的适用范围:RedHat.RHEL.Ubuntu.CentOS. 1.语法 ...