分类 C++ 下的文章

汇编中调用RegCreateKeyExW等函数返回998错误码的问题解决

  最近在集中测试install.stub的汇编优化版,测试到这里出现了莫名其妙的问题,同样的C代码调用RegCreateKeyExW没有问题,但是在MASM中调用就会返回998错误

  后来在这里找到了答案http://technet.microsoft.com/en-us/library/ms724844%28v=VS.85%29.aspx

  原来RegCreateKeyExW传入的字符串需要对齐到字,即简单来说就是Unicode字符串的首地址要能够被2整除,可惜MSDN文档中没有这一提醒。。。

修正方法:

1、MASM中的静态字符串没有对齐到字,所以需要手动操作,最好把WSTR集中放到一起,因为WSTR本身是字对齐的,如果调试中发现上述的错误在前面通过补0进行对齐即可

2、也可以将字符串拷贝到栈上,因为栈上分配的空间是对齐到双字的,所以不存在对齐到字的问题

从Windows(x86)到Linux(x64)的代码移植总结

由于最新的引擎要支持x64 Linux,算是实战了一把代码移植技术,这里就将移植相关的技术做一总结,和各位沟通交流,也方便作为手册随时查阅

这里总结的是从win32到 x64 Linux的移植,其中涉及到32到64移植和windows到Linux的移植过程,这里先说明一下。

首先就Windows和Linux之间的差异列举如下,开发环境基于VC 2005(Windows 7)和GCC(x64 Linux)

1、基本类型间差异

  有做过windows 32位到windows64位移植的朋友应该清楚,VC 64对VC 32的兼容性很高,除了指针的长度从32位提高到了64位,其他基本类型没有任何差别,这样使得windows中的32到64移植基本没有什么工作量,只要代码中没有指针到其他类型的强制转换,基本上代码拉过来直接编译一下就能用了。这里的32到64的变化有一个名词就叫LLP64(long long and pointer 64)。这样的话VC 64中要使用64 bit的整形,就需要使用long long类型。

  在GCC 64中,采用的则是另外一种模型,即LP64,其中基本类型long从32位变换为64位。在LP64模型中,牺牲掉了兼容性,这样导致代码中凡是涉及long的地方都要谨慎对待,尤其在编写32位代码过程中将long认为和int等同,则更有可能存在潜在问题。

  除此之外,wchar_t的长度也是不一样的,VC下是2字节,GCC下则是4字节。

2、数据对齐问题

  由于在x86架构下,32位和64位数据对齐可以说不是什么问题,只是VC和GCC之间使用的关键字不同,使用方法也不太一样,所以有必要简单提一下。

3、字节序问题

  由于这次是在x86和x86_64之间进行移植,都是little endian的,所以字节序问题不存在,如果有朋友需要将代码移植到其他平台,比如PowerPC,Sparc等,则需要重点关注了。

4、字符串编码问题

  针对char和wchar_t,就有窄字符编码和宽字符编码的问题。Windows中窄字符就是MBCS编码,即多字节编码,如中文windows就是GBK(这一系列GB2312,GBK,GB18030等),MBCS编码兼容ASCII码。宽字符编码就是windows宣传的Unicode编码,采用UTF16编码。而linux中的主流操作系统窄字符编码即UTF8(可配,系统默认一般为UTF8),而宽字符编码为UTF32。

5、OS相关API不一致问题

  Windows是非类Unix系的操作系统,不包含POSIX接口,只有自己的Windows API。包括多线程API,线程同步API,文件操作API,Socket API,管道API等等,不管是函数名和参数都是有很大差异的。

  有一点有必要独立拎出来提一下,那就是Windows的Crital secion是递归锁,而linux下默认则不是。

6、编译环境的不一致

  Windows下的编译环境,即这里的VC,她是一个集成开发环境,包含强大而智能的编译环境,开发者需要做的就是点一下Build命令即可,编译依赖等问题根本不是问题。

  而linux下则更多需要开发者动手自己操作,这也是linux常归于高手的一个原因,因为她不喜欢懒人。

  了解了这些差异,基本上能够预见移植过程中的常见问题,这也是知己知彼的道理,针对这些差异都有对应的一些解决方案,或者说是屏蔽方案,总的一个思想就是,通过封装统一平台差异,无法统一的就采用条件编译,针对不同平台编写功能表征一致的代码。

  一切以统一为先,毕竟跨平台开发最好的就是一份代码处处编译,这样才能减少多平台维护的成本,尽管这样会影响性能,但是对比来看性能的些许损失还是值得的。

好了,这里说说针对不同差异的一些解决办法

1、基本类型差异

针对类型差异,需要通过统一类型来进行屏蔽,简单来说就是通过typedef或者#define对不同平台间的类型进行统一抽象,相同类型统一为相同位长。

例如:
typedef sp_byte_t unsigned char
typedef sp_int8_t sp_byte_t
typedef sp_int16_t short
typedef sp_int32_t int
等等

这样可以程度避免由于基本类型位长不同而导致的一些列问题。对于平台相关的基本类型,比如long,尽量不用,这也是多一事不如少一事的道理,如果确实需要进行大数运算而涉及到64位整型,那么需要注意基本类型间转换而导致的溢出或者数据丢失,而且如果要使用类printf的函数,输出的指示符也要注意。

如果涉及到32位进程和64位进程间的通信,也要注意平台间类型的使用,否则由于位长的不一致,而导致数据错乱,比如size_t的使用,等等

这里顺带着把对其问题说说

在VC中使用#pragma pack可以指定结构体的对齐方式

例如:

#pragma pack(1)
struct s {
    char t1;
    char t2;
    int t3;
}
#pragma pack()

而在GCC中则需要使用attribute属性设置

例如:

struct s {
    char t1;
    char t2;
    int t3;
} __attribute__((packed))

2、字符串编码问题

这里说说我采用的一种策略。即linux模拟windows的字符串编码模式

这样就涉及分别统一窄字符和宽字符编码

需要注意如下几个问题:

① 确保源代码文件能够正常解析

由于vc中的默认源代码编码为GBK,使用GCC编译时,需要指定源代码编码名称,否则以GCC默认的UTF8解码会出现乱码或者报错,通过参数-finput-charset=GBK设置即可

② 确保编译后可执行文件中字符串编码正确

上面的参数只是告诉GCC如何正常解析源代码文件,当完成编译后,生成可执行文件时,GCC保存到可执行文件中的字符串编码默认是UTF8,所以这里也要修改为GBK,通过参数-fexec-charset=GBK

③ 确保运行时宽窄字符能够正常转换

这涉及到locale,因为C运行库中的mbstowcs和wcstombs是locale相关的,linux下默认的是UTF8,所以这里也要设置,否则运行时会出现转码错误。

可以在程序开始时设置环境变量LC_ALL=zh_CN.GBK即可

或者在运行中调用setlocale

④ 上面提到wchart_t在两系统的长度是不一样的

在windows中采用UTF16编码,在Linux中采用UTF32编码。一般没有什么问题,但是如果windows程序需要和linux程序进行交互的话,可能需要注意UTF32和UTF16之间的编码转换。

或者涉及到Java封装,比如我这次移植的内核,需要对C接口进行Java封装,涉及到wchar_t到Java的UTF16编码字符串(Java中采用unsigned short表示)转换,就需要注意着一点

通过上面的一系列配置,linux中的仿windows编码环境也就弄好了,这样能够减少windows代码移植的工作量。

当然还有其他的方式,比如环境无关的统一字符串编码等等,就留给今后探索吧

3、OS相关API不一致问题

这类问题是硬伤,需要一些的底层库进行跨平台封装,以统一特性。

可以自行开发,也可以使用开源库

这里推荐一些开源的库,用起来确实不错

① Boost.Thread

这是Boost封装的多线程库,包括一些有用的同步对象,目前已经作为标准放入了C++ 11中。

② Boost.filesystem

这是Boost提供的文件系统操作库,包括常见的删除文件/夹,复制文件/夹,创建文件夹等操作,只是该库采用异常处理处理错误信息,所以需要注意try catch

③ Boost.Asio

这是Boost提供的异步网络底层库,效率不错

④ ACE

重量级的网络库,封装了常见的网络通信相关的设计模式,如果需要开发大型的网络通信平台,可以考虑使用该库。

4、编译环境不一致问题

有了VC的出现,编译问题变得很简单,因为微软做了大量的底层工作,所以门槛很低,所以Windows下的编译问题可以忽略不计。

可是到了Linux下,编译环境的使用是需要用户了解相关的脚边知识的,所以无形中加大了Linux下大规模软件的构建门槛。

因为在Linux下需要做的更多,所以这里先说说Linux下编译环境相关的一些知识。

① make & makefile

这是基本的编译工具,很多高级的构建工具多基于此。

Make其实是基于依赖的强大构建系统,主要包括三要素:依赖,目标,策略

目标即想要生成的对象。

依赖即生成该对象所需要的其他模块。

策略即如何生成该对象的方法。

Make的使用说简单也简单,说复杂也复杂,这里难以展开,有兴趣的朋友可以搜索相关技术文章。

② CMake

这里就不提及automake & autoconf了,没用过也不太喜欢用。

CMake可以认为是automake的替代品,她通过内建的脚本语言描述构建策略,简化了对对底层命令的直接操作,而且抽象了构建模式,比如,要构建一个可执行程序,需要告诉她的只有编译该可执行文件所需要的源代码文件,她会帮你完成源代码分析,并构造出依赖树,最后调用适当的编译器完成构建。

同时,CMake还具有很高的自定义能力,强大的构建模式完全不影响你按需修改自己的构建策略。

最后还有一点,那就是CMake是跨平台的,不同平台使用同一CMake脚本,针对不同平台她会调用相关的编译器,当然如果你有多个编译器,也是可以自由指定的。

CMake是KDE的基础构建工具,不熟悉CMake,我想KDE应该了解吧^_^。

③ SCons

SCons是用python编写又一跨平台构建工具,她完全替换了make系统,而且构建脚本语言就是python语言,所以用起来确实很不错,这里也顺便推荐一下。考虑到SCons是基于python的脚本语言构建的,所以针对大型系统的构建效率如何就不太确定了,所以使用前最好先评估一下系统规模。

(转)linux 头文件、库文件查找顺序

include的header文件,连结数据库,系统定义,总共有下列来源指定gcc去那找。

当初在编译时指定的(在~gcc/gcc/collect2.c:locatelib()

写在specs内的

后来用-D -I -L指定的

gcc环境变量设定(编译的时候)

ld.so的环境变量(这是run time的时候)

一、头文件

gcc 在编译时如何去寻找所需要的头文件 :

※所以header file的搜寻会从-I开始

※然后找gcc的环境变量 C_INCLUDE_PATH,CPLUS_INCLUDE_PATH,OBJC_INCLUDE_PATH

※再找内定目录

/usr/include

/usr/local/include

/usr/lib/gcc-lib/i386-linux/2.95.2/include

/usr/lib/gcc-lib/i386-linux/2.95.2/../../../../include/g -3

/usr/lib/gcc-lib/i386-linux/2.95.2/../../../../i386-linux/include

库文件但是如果装gcc的时候,是有给定的prefix的话,那么就是

/usr/include

prefix/include

prefix/xxx-xxx-xxx-gnulibc/include

prefix/lib/gcc-lib/xxxx-xxx-xxx-gnulibc/2.8.1/include

二、库文件

cos()等函式库的选项要多加 -lm

编译的时候:

※gcc会去找-L

※再找gcc的环境变量LIBRARY_PATH

※再找内定目录 /lib /usr/lib /usr/local/lib 这是当初compile gcc时写在程序内的

三、运行时动态库的搜索路径

1、在配置文件/etc/ld.so.conf中指定动态库搜索路径

2、通过环境变量LD_LIBRARY_PATH指定动态库搜索路径(当通过该环境变量指定多个动态库搜索路径时,路径之间用冒号":"分隔)

3、在编译目标代码时指定该程序的动态库搜索路径(还可以在编译目标代码时指定程序的动态库搜索路径。

这是通过gcc 的参数"-Wl,-rpath,"指定(如例3所示)。当指定多个动态库搜索路径时,路径之间用冒号":"分隔)

4、默认的动态库搜索路径/lib

5、默认的动态库搜索路径/usr/lib

可以通过执行可执行文件pos得到的结果不同获知其搜索到了哪个动态库,从而获得第1个动态库搜索顺序,然后删除该动态库,

再执行程序pos,获得第2个动态库搜索路径,再删除第2个被搜索到的动态库,

如此往复,将可得到Linux搜索动态库的先后顺序。

程序pos执行的输出结果和搜索到的动态库的对应关系如表1所示

程序pos输出结果 使用的动态库 对应的动态库搜索路径指定方式

./ ./libpos.so 编译目标代码时指定的动态库搜索路径

/root/test/env/lib /root/test/env/lib/libpos.so 环境变量LD_LIBRARY_PATH指定的动态库搜索路径

/root/test/conf/lib /root/test/conf/lib/libpos.so 配置文件/etc/ld.so.conf中指定的动态库搜索路径

/lib /lib/libpos.so 默认的动态库搜索路径/lib

/usr/lib /usr/lib/libpos.so 默认的动态库搜索路径/usr/lib

综合以上结果可知,动态库的搜索路径搜索的先后顺序是:

  1. 编译目标代码时指定的动态库搜索路径;
  2. 环境变量LD_LIBRARY_PATH指定的动态库搜索路径;
  3. 配置文件/etc/ld.so.conf中指定的动态库搜索路径;
  4. 默认的动态库搜索路径/lib;
  5. 默认的动态库搜索路径/usr/lib。

(转)Visual C++ 和 GNU g++ 内部探密

  Visual C++ 和 GNU g++ 都为 cl 编译器提供了一些选项。尽管您可以使用 cl 作为独立的工具进行编译工作,但是,Visual C++ 提供了一种灵活的集成开发环境 (IDE) 以设置编译器选项。使用 Visual Studio® 开发的软件通常使用了一些编辑器特定的和平台相关的特性,可以使用编译器或者连接器来控制这些特性。当您在不同的平台(使用了不同的编译器或者工具链)之 间移植源代码的时候,了解编译器的相关选项,这一点是非常重要的。这部分内容深入分析了一些最有价值的编译器选项。
  

启用字符串池

  
  可以考虑下面的代码片段:

  char *string1= "This is a character buffer";
  char *string2= "This is a character buffer";

  
  如果在 Visual C++ 中启用了字符串池选项 [/GF],那么在执行期间,将在程序的映像中仅保存该字符串的单个副本,且 string1 与 string2 相等。需要说明的是,g++ 的行为正好与它相反,在缺省情况下,string1 与 string2 相等。要在 g++ 中禁用字符串池,您必须将 -fwritable-strings 选项添加到 g++ 命令行。
  

使用 wchar_t

  
  C++ 标准定义了 wchar_t 宽字符类型。如果将 /Zc:wchar_t 选项传递给编译器,那么 Visual C++ 会将 wchar_t 作为本地类型。否则,需要包含一些实现特定的 Header,如 windows.h 或者一些标准的 Header(如 wchar.h)。g++ 支持本地 wchar_t 类型,并且不需要包括特定的 Header。请注意,在不同的平台之间,wchar_t 的大小是不相同的。您可以使用 -fshort-wchar g++ 选项将 wchar_t 的大小强制规定为两个字节。
  

C++ 运行时类型识别 (Run Time Type Identification) 的支持

  
  如果源代码没有使用 dynamic_cast 或者 typeid 操作符,那么就可以禁用运行时类型识别 (RTTI)。在缺省情况下,Visual Studio 2005 中打开了 RTTI(即 /GR 开关处于打开状态)。可以使用 /GR- 开关在 Visual Studio 环境中禁用 RTTI。禁用 RTTI 可能有助于产生更小的可执行文件。请注意,在包含 dynamic_cast 或者 typeid 的代码中禁用 RTTI,可能会产生一些负面的影响,包括代码崩溃。可以考虑清单 1 中的代码片段。
  
  清单 1. 演示 RTTI 的代码片段

#include <iostream>
struct A {
    virtual void f()
    { std::cout << "A::f\n"; }
};

struct B : A {
    virtual void f()
    { std::cout << "B::f\n"; }
};

struct C : B {
    virtual void f()
    { std::cout << "C::f\n"; }
};

int main (int argc, char** argv )
{
    A* pa = new C;
    B* pb = dynamic_cast<B*> (pa);
    if (pb)
        pb->f();
        return 0;
}

  
  为在 Visual Studio IDE 之外独立的 cl 编译器中编译这个代码片段,需要显式地打开 /GR 切换开关。与 cl 不同,g++ 编译器不需要任何特殊的选项以打开 RTTI。然而,与 Visual Studio 中的 /GR- 选项一样,g++ 提供了 -fno-rtti 选项,用以显式地关闭 RTTI。在 g++ 中使用 -fno-rtti选项编译这个代码片段,将报告编译错误。然而,即使 cl 在编译这个代码时不使用 /GR 选项,但是生成的可执行文件在运行时将会崩溃。
  

异常处理

  
  要在 cl 中启用异常处理,可以使用 /GX 编译器选项或者 /EHsc。如果不使用这两个选项,try 和 catch 代码仍然可以执行,并且系统执行到throw 语句时才会调用局部对象的析构函数。异常处理会带来性能损失。因为编译器将为每个 C++ 函数生成进行堆展开的代码,这种需求将导致更大的可执行文件、更慢的运行代码。对于特定的项目,有时无法接受这种性能损失,那么您需要关闭该特性。要禁用 异常处理,您需要从源代码中删除所有的 try 和 catch 块,并使用 /GX- 选项编译代码。在缺省情况下,g++ 编译器启用了异常处理。将 -fno-exceptions 选项传递给 g++,会产生所需的效果。请注意,对包含 try、catch 和 throw 关键字的源代码使用这个选项,可能会导致编译错误。您仍然需要手工地从源代码中删除 try 和 catch 块(如果有的话),然后将这个选项传递给 g++。可以考虑清单 2 中的代码。
  
  清单 2. 演示异常处理的代码片段

  #include <iostream>
  using namespace std;
  
  class A { public: ~A () { cout << "Destroying A "; } };
  void f1 () { A a; throw 2; }
  
  int main (int argc, char** argv ) {
  try { f1 (); } catch (...) { cout << "Caught!\n"; }
  return 0;
  }

  
  下面是 cl 和 g++ 在使用以及不使用该部分中所介绍的相关选项时得到的输出结果:
  
  cl 使用 /GX 选项: Destroying A Caught!
  cl 不使用 /GX 选项: Caught!
  g++ 不使用 -fno-exceptions: Destroying A Caught!
  g++ 使用 -fno-exceptions:编译时间错误
  

循环的一致性

  
  对于循环的一致性,可以考虑清单 3 中的代码片段。
  

清单 3. for 循环的一致性

  int main (int argc, char** argv )
  {
  for (int i=0; i<5; i++);
  i = 7;
  return 0;
  }

  
  根据 ISO C++ 的指导原则,这个代码将无法通过编译,因为作为循环中的一部分而声明的 i 局部变量的范围仅限于该循环体,并且在该循环之外是不能进行访问的。在缺省情况下,cl 将完成这个代码的编译,而不会产生任何错误。然而,如果 cl 使用 /Zc:forScope 选项,将导致编译错误。g++ 的行为正好与 cl 相反,对于这个测试将产生下面的错误:
  
  error: name lookup of 'i' changed for new ISO 'for' scoping
  
  要想禁止这个行为,您可以在编译期间使用 -fno-for-scope 标志。
  

使用 g++ 属性

  
  Visual C++ 和 GNU g++ 都为语言提供了一些非标准的扩展。g++ 属性机制非常适合于对 Visual C++ 代码中的平台特定的特性进行移植。属性语法采用格式 attribute ((attribute-list)),其中属性列表是以逗号分隔的多个属性组成的列表。该属性列表中的单个元素可以是一个单词,或者是一个单词后面紧跟使用括号括起来的、该属性的可能的参数。这部分研究了如何在移植操作中使用这些属性。
  

函数的调用约定

  
  您可以使用 Visual Studio 中特定的关键字,如cdecl、__stdcall 和 __fastcall,以便向编译器说明函数的调用约定。表 1 对有关的详细内容进行了汇总。
  
  表 1. Windows 环境中的调用约定
  调用约定 隐含的语义
  __cdecl(cl 选项:/Gd) 从右到左地将被调用函数的参数压入堆栈。在执行完毕之后,由调用函数将参数弹出堆栈。
  __stdcall(cl 选项:/Gz) 从右到左地将被调用函数的参数压入堆栈。在执行完毕之后,由调用函数将参数弹出堆栈。
  __fastcall(cl 选项:/Gr) 将最前面的两个参数传递到 ECX 和 EDX 寄存器中,同时将所有其他参数从右到左地压入堆栈。由被调用函数负责清除执行后的堆栈。
  
  用以表示相同行为的 g++ 属性是 cdecl、stdcall 和 fastcall。清单 4 显示了 Windows® 和 UNIX® 中属性声明风格的细微差别。
  
  清单 4. Windows 和 UNIX 中的属性声明风格
  
  Visual C++ Style Declaration:
  double __stdcall compute(double d1, double d2);
  
  g++ Style Declaration:
  double __attribute
((stdcall)) compute(double d1, double d2);
  
  

结构成员对齐

  
  /Zpn 结构成员对齐选项可以控制结构在内存中的对齐方式。例如,/Zp8 以 8 个字节为单位对结构进行对齐(这也是缺省的方式),而 /Zp16则以 16 个字节为单位对结构进行对齐。您可以使用 aligned g++ 属性来指定变量的对齐方式,如清单 5 中所示。
  
  清单 5. Windows 和 UNIX 中结构成员的对齐方式
  
  Visual C++ Style Declaration with /Zp8 switch:
  struct T1 { int n1; double d1;};
  
  g++ Style Declaration:
  struct T1 { int n1; double d1;} attribute((aligned(8)));
  
  
  然而,对齐属性的有效性将受到固有的连接器局限性的限制。在许多系统中,连接器只能够以某个最大的对齐方式对变量进行对齐。
  

Visual C++ declspec nothrow 属性

  
  这个属性可以告诉编译器,使用该属性声明的函数以及它调用的后续函数都不会引发异常。使用这个特性可以对减少整体代码的大小进行优化,因为在缺省情况下,即使代码不会引发异常,cl 仍然会为 C++ 源代码生成堆栈展开信息。您可以使用 nothrow g++ 属性以实现类似的目的,如清单 6 中所示。
  
  清单 6. Windows 和 UNIX 中的 nothrow 属性
  
  Visual C++ Style Declaration:
  double declspec(nothrow) sqrt(double d1);
  
  g++ Style Declaration:
  double __attribute
((nothrow)) sqrt(double d1);
  
  
  一种更加具有可移植性的方法是,使用标准定义的风格: double sqrt(double d1) throw ();.
  

Visual C++ 和 g++ 之间相似的内容

  
  除了前面的一些示例之外,Visual C++ 和 g++ 属性方案之间还存在一些相似的内容。例如,这两种编译器都支持noinline、noreturn、deprecated 和 naked 属性。
  
  从 32 位的 Windows 移植到 64 位的 UNIX 环境时的潜在缺陷
  
  在 Win32 系统中开发的 C++ 代码是基于 ILP32 模型的,在该模型中,int、long 和指针类型都是 32 位的。UNIX 系统则遵循 LP64 模型,其中 long 和指针类型都是 64 位的,但是 int 仍然保持为 32 位。大部分的代码破坏,都是由于这种更改所导致的。这部分简要讨论了您可能会遇到的两个最基本的问题。从 32 位到 64 位系统的移植是一个非常广阔的研究领域。有关这个主题的更多信息,请参见参考资料部分。
  

数据类型大小方面的差别

  
  某些数据类型在 ILP32 和 LP64 模型中是相同的,使用这样的数据类型才是合理的做法。通常,您应该尽可能地避免使用 long 和 pointer 数据。另外,通常我们会使用 sys/types.h 标准 Header 中定义的数据类型,但是这个文件中的一些数据类型(如 ptrdiff_t, size_t 等等)的大小,在 32 位模型和 64 位模型之间是不一样的,您在使用时必须小心。
  

个别数据结构的内存需求

  
  个别数据结构的内存需求可能会发生改变,这依赖于编译器中实现对齐的方式。可以考虑清单 7 中的代码片段。
  
  清单 7. 错误的结构成员对齐方式

  struct s {
  int var1; // hole between var1 and var2
  long var2;
  int var3; // hole between var3 and ptr1
  char* ptr1;
  };
  // sizeof(s) = 32 bytes

  
  在 LP64 模型中,long 和 pointer 类型都以 64 位为单位进行对齐。另外,结构的大小以其中最大成员的大小为单位进行对齐。在这个示例中,结构 s 以 8 个字节为单位进行对齐,s.var2 变量同样也是如此。这将导致在该结构中出现一些空白的地方,从而使内存膨胀。清单 8 中的重新排列导致该结构的大小变为 24 个字节。
  
  清单 8. 正确的结构成员对齐方式

  struct s {
        int var1;
        int var3;
        long var2;
        char* ptr1;
  };
  // sizeof(s) = 24 bytes

  

移植多线程的应用程序

  
  从技术上讲,一个线程是操作系统可以调度运行的独立指令流。在这两种环境中,线程都位于进程之中,并且使用进程的资源。只要线程的父进程存在,并且 操作系统支持线程,那么线程将具有它自己的独立控制流。它可能与其他独立(或者非独立)使用的线程共享进程资源,如果它的父进程结束,那么它也将结束。下 面对一些典型的应用程序接口 (API) 进行了概述,您可以使用这些 API 在 Windows 和 UNIX 环境中建立多线程的项目。对于 WIN32 API,所选择的接口是 C 运行时例程,考虑到简单性和清晰性,这些例程符合可移植操作系统接口(Portable Operating System Interface,POSIX)的线程。
  
  请注意:由于本文篇幅有限,我们不可能为编写多线程应用程序的其他方式提供详细的介绍。
  

创建线程

  
  Windows 使用 C 运行时库函数中的 _beginthread API 来创建线程。您还可以使用一些其他的 Win32 API 来创建线程,但是在后续的内容中,您将仅使用 C 运行时库函数。顾名思义,_beginthread() 函数可以创建一个执行例程的线程,其中将指向该例程的指针作为第一个参数。这个例程使用了 __cdecl C 声明调用约定,并返回空值。当线程从这个例程中返回时,它将会终止。
  
  在 UNIX 中,可以使用 pthread_create() 函数完成相同的任务。pthread_create() 子程序使用线程参数返回新的线程 ID。调用者可以使用这个线程 ID,以便对该线程执行各种操作。检查这个 ID,以确保该线程存在。
  

删除线程

  
  _endthread 函数可以终止由 _beginthread() 创建的线程。当线程的顺序执行完成时,该线程将自动终止。如果需要在线程中根据某个条件终止它的执行,那么 _endthread() 函数是非常有用的。
  
  在 UNIX 中,可以使用 pthread_exit() 函数实现相同的任务。如果正常的顺序执行尚未完成,这个函数将退出线程。如果 main() 在它创建的线程之前完成,并使用 pthread_exit() 退出,那么其他线程将继续执行。否则,当 main() 完成的时候,其他线程将自动终止。
  

线程中的同步

  
  要实现同步,您可以使用互斥信号量。在 Windows 中,CreateMutex() 可以创建互斥信号量。它将返回一个句柄,任何需要互斥信号量对象的函数都可以使用这个句柄,因为对这个互斥信号量提供了所有的访问权限。当拥有这个互斥信号量的线程不再需要它的时候,可以调用ReleaseMutex(),以便将它释放回系统。如果调用线程并不拥有这个互斥信号量,那么这个函数的执行将会失败。
  
  在 UNIX 中,可以使用 pthread_mutex_init() 例程动态地创建一个互斥信号量。这个方法允许您设置互斥信号量对象的相关属性。或者,当通过 pthread_mutex_t 变量声明它的时候,可以静态地创建它。要释放一个不再需要的互斥信号量对象,可以使用 pthread_mutex_destroy()。
  

移植多线程应用程序的工作示例

  
  既然您已经掌握了本文前面所介绍的内容,下面让我们来看一个小程序示例,该程序使用在主进程中执行的不同线程向控制台输出信息。清单 9是 multithread.cpp 的源代码。
  
  清单 9. multithread.cpp 的源代码

#include <stdio.h>
#include <stdlib.h>

#ifdef WIN32
#include <windows.h>
#include <string.h>
#include <conio.h>
#include <process.h>
#else
    #include <pthread.h>
#endif

#define MAX_THREADS 32

#ifdef WIN32
void InitWinApp();
void WinThreadFunction( void* );
void ShutDown();

HANDLE mutexObject;
#else
    void InitUNIXApp();
void* UNIXThreadFunction( void *argPointer );

pthread_mutex_t mutexObject = PTHREAD_MUTEX_INITIALIZER;
#endif

int threadsStarted; // Number of threads started

int main()
{
    #ifdef WIN32
    InitWinApp();
    #else
        InitUNIXApp();
    #endif
}

#ifdef WIN32
void InitWinApp()
{
    mutexObject = CreateMutex( NULL, FALSE, NULL );
    if(mutexObject == NULL && GetLastError() != ERROR_SUCCESS)
    {
        printf("failed to obtain a proper mutex for multithreaded application");
        exit(1);
    }
    threadsStarted = 0;
    for(;threadsStarted < 5 && threadsStarted < MAX_THREADS;
            threadsStarted++)
    {
        _beginthread( WinThreadFunction, 0, &threadsStarted );
    }
    ShutDown();
    CloseHandle( mutexObject );
    getchar();
}

void ShutDown()
{
    while ( threadsStarted > 0 )
    {
        ReleaseMutex( mutexObject );
        threadsStarted--;
    }
}

void WinThreadFunction( void *argPointer )
{
    WaitForSingleObject( mutexObject, INFINITE );
    printf("We are inside a thread\n");
    ReleaseMutex(mutexObject);
}

#else
void InitUNIXApp()
{
    int count = 0, rc;
    pthread_t threads[5];

    while(count < 5)
    {
        rc = pthread_create(&threads[count], NULL, &UNIXThreadFunction, NULL);
        if(rc)
        {
            printf("thread creation failed");
            exit(1);
        }
        count++;
    }

    // We will have to wait for the threads to finish execution otherwise
    // terminating the main program will terminate all the threads it spawned
    for(;count >= 0;count--)
    {
        pthread_join( threads[count], NULL);
    }
    //Note : To destroy a thread explicitly pthread_exit() function can be used
    //but since the thread gets terminated automatically on execution we did
    //not make explicit calls to pthread_exit();
    exit(0);
}

void* UNIXThreadFunction( void *argPointer )
{
    pthread_mutex_lock( &mutexObject );
    printf("We are inside a thread\n");
    pthread_mutex_unlock( &mutexObject );
}
#endif

  
  我们利用 Visual Studio Toolkit 2003 和 Microsoft Windows 2000 Service Pack 4 通过下面的命令行对 multithread.cpp 的源代码进行了测试:
  
  cl multithread.cpp /DWIN32 /DMT /TP
  
  我们还在使用 g++ 编译器版本 3.4.4 的 UNIX 平台中通过下面的命令行对它进行了测试:
  
  g++ multithread.cpp -DUNIX -lpthread
  
  清单 10 是该程序在两种环境中的输出。
  
  清单 10. multithread.cpp 的输出
  
  We are inside a thread
  We are inside a thread
  We are inside a thread
  We are inside a thread
  We are inside a thread
  
  

结束语

  
  在两种完全不同的平台(如 Windows 和 UNIX)之间进行移植,需要了解多个领域的知识,包括了解编译器和它们的选项、平台特定的特性(如 DLL)以及实现特定的特性(如线程)。本系列文章介绍了移植工作的众多方面。有关这个主题的更深入信息,请参见参考资料部分。
  

(转)C/C++源文件编码方式

好文一篇,转来共赏^_^

"汉字"
GBK编码:BA BA , D7 D6
UTF-8编码:E6 B1 89, E5 AD 97
UTF-16BE编码:6C 49, 5B 57

2356注:文中的GCC指GCC中的C/C++编译器,CL指Visual C++编译器

两种常用编译器gcc,cl中对Unicode字面值的实现:

GCC
gcc中跟编码方式转换有关的三个编译选项:

-finput-charset=charset,此选项指定源文件本身的编码方式,默认为UTF-8(有无BOM均可)。例如当我们的源代码文件保存为GBK时,则也应当将此选项的值指定为GBK。
-fwide-exec-charset=charset,此选项指定宽字符或宽字符串的字面值常量的内部编码方式,默认为UTF-32或UTF-16,对应wchar_t的宽度。wchar_t的宽度依赖平台实现,windows 实现为2字节宽,linux 实现为4字节宽。例如指定此选项为GBK,则宽字符或宽字符串常量将会以GBK编码方式存储而不是默认的UTF-32或UTF-16编码方式。
-fexec-charset=charset,此选项指定窄字符或窄字符串的字面值常量的内部编码方式,默认为UTF-8。例如指定此选项为GBK,则窄字符或窄字符串常量将会以GBK编码方式存储而不是默认的UTF-8编码方式。

有了以上铺垫,下面两条语句的意义就很清楚了:
char cstr[] = "汉字"; //将"汉字"由-finput-charset指定的编码方式转换成由-fexec-charset指定的编码方式。
wchar_t wstr[] = L"汉字"; //将"汉字"由-finput-charset指定的编码方式转换成由-fwide-exec-charset指定的编码方式。

注1:gcc在4.4.0版本以前存在bug,不能编译带BOM的UTF-8源文件。参见http://gcc.gnu.org/bugzilla/show_bug.cgi?id=33415

注2:Qt自带的mingw gcc 4.4.0貌似有bug,不能支持-finput-charset选项;TDM gcc 4.5.1则没有这个问题。
注3:即便TDM gcc 也不能支持UTF-16,无论BE或者LE。症状是好像只要包含了头文件--无论是标准库的头文件还是Qt的头文件,就无法编译通过,不包含头文件则是可以编译通过的。

CL
cl 没有提供类似gcc指定编码方式的编译选项,只能使用默认值或靠自动识别:

对应gcc的-finput-charset,cl 能自动识别源文件的编码方式,cl 支持的源文件编码方式有:UTF-16(BE,LE均可,有无BOM均可),UTF-8(带BOM),除此之外的源文件均认为是ANSI编码方式(包括不带BOM的UTF-8)。所以使用VC编译不带BOM的UTF-8文件时要特别注意,vc会将此文件当做ANSI也就是GBK编码格式。否则经常会出这样的警告:该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存为 Unicode 格式以防止数据丢失。

对应gcc的-fwide-exec-charset, cl 固定使用UTF-16编码方式。
对应gcc的-fexec-charset, cl 固定使用ANSI编码方式。

再看同样的语句在 cl 下的行为:

对于源文件的编码方式为UTF-16BE,UTF-16LE, UTF-8(带BOM):
char cstr[] = "汉字"; //将源文件中的"汉字"从当前编码方式转换成ANSI编码方式,即GBK。
wchar_t wstr[] = L"汉字"; //将源文件中的"汉字"从当前编码方式转换成UTF-16编码方式。

对于其他编码方式的源文件(包括无BOM的UTF-8),实际上会被当做ANSI编码,即GBK:
char cstr[] = "汉字"; //认为已经是ANSI编码了,故没有转换发生,cstr中依旧是"汉字"的原始编码。例如,如果源文件的编码方式为无BOM的UTF-8,则cstr中依旧是"汉字"的UTF-8编码。
wchar_t wstr[] = L"汉字"; //无论源文件采用何种编码方式,都将"汉字"当做ANSI编码,转换成UTF-16编码。这样硬转的结果当然是除了当前是GBK编码时能正常转换,其余各种编码方式得到的只能是乱码一堆。

有关cl对unicode的支持请参考:http://msdn.microsoft.com/en-us/library/xwy0e8f2(v=VS.90).aspx

C++1x

面对如此混乱的局面,c++1x提供了更多字符串字面值表示法:
"string of char characters in some implementation defined encoding" - char
u8"string of utf8 chars" - char
u"string of utf16 chars" - char16_t
U"string of utf32 chars" - char32_t
L"string of wchar_t in some implementation defined encoding" - wchar_t