我目前工作中的大多数项目是在aix上跑的,最近有个aix的c++项目要移植到linux上,而我个人喜欢使用VC作为开发工具。因为这样,需要对项目windows,aix,linux上的移植,在这个过程中作了些总结。
现假设平台与编译工具对应如下:
windows――vc
aix――xlc
linux――gcc
目录:
1.3.1. 派生类如果使用基类的函数或成员变量,应该在前面加“this->”,或在类声明中加入“using 基类函数;”
1.3.2. 使用了基类或其它模板类里定义的类型,要在类型前加typename
1. c++标准
c++的发展已经有几十年历史了,但标准是在1998年制定的,在标准之前,出现了很多编译器,各个编译器对于语法上存在一定差异。
像vc与xlc这种是属于企业级应用的工具,会追求向前兼容。以保证以前的代码能够使用,所以如果有不怎么标准的代码,编译器也会让它通过。而gcc比较偏向于研究应用的工具,会追求标准化。
这就产生了有些代码在vc或xlc上通过,而在gcc上编不过的情况。
1.1. 变量、函数重定义
1.1.1. 避免在头文件里定义变量。
假设a.h定义了一个变量int error;
b.cpp中#include “a.h”
c.cpp中#include “a.h”
则在同一个程序中,error定义了两次,编译器在链接的时候报错。
1.1.2. 避免在头文件里定义外部函数
函数的道理其实跟变量差不多,也会出现重定义。但如果一定要把函数写在头文件可以有三种方法:
a.加上inline
b.加上static
c.使用一个空的名字空间把函数包起来。
变量其实也可以用static或空名字空间来解决重定义的问题,但变量的问题比函数复杂,在头文件里使用了全局变量不但带来编译的问题,还会容易引入潜在错误,如果要使用多线程,则使到问题越发复杂。
如果一定要使用全局变量有一个比较好的方法,定义一个全局函数,函数是定义一个静态变量,函数返回该变量的一个引用,事例如下:
// a.h中
int& theError();
// a.cpp中
int& theError()
{
staticint s_error;
return s_error;
}
哪个地方要使用error这个全局变量的话,#include”a.h”后直接用theError()就好了。比如你可以这样用:
theError() = -1;
另外,也不建议在头文件中引入名字空间。如果在头文件中using namespace std,使用字符串组件用“string”的名字就可以了,但在stl标准化之前(甚至之后),大量的字符串组件存在,别人也可能使用“string”这个名字。一不小心就冲突了。
1.2. 函数返回值
c语言以及c++98标准制定之前的c++,函数声明或定义如果没有返回值,则隐式定义为返回int。但c++98标准之后,规定函数必须制定返回值。
对于类似于以下的代码:
fun(int, char);
早期的gcc,以及目前的大多数编译器都可以通过,但比较新版的gcc是会报错的。
1.3. 模板
*注:以下属于编译器细节,如果不想了解或看完后还是不了解的话,可以略过:
c++标准制定前,模板相当于宏,在用到模板的地方会根据模板参数直接把模板代码翻译为非模板的代码。
如果事情是这样先翻译后编译的话,模板在各个编译器上应该是差不多的了。但c++委员会为了使到模板的错误信息(即在写模板的时候就把代码写错了)更加易于理解,以及为了在解析模板的阶段检查潜在的隐患,规定模板的查找分为两个阶段,第一阶段在看到模板的定义时,第二阶段是模板实例化时。并且标准规定“对于模板中的非依赖型名称,将会在看到的第一时间进行查找”。有了这个概念后,让我们考虑下面的例子
template<typename T>
class DD : public Base<T>
{
public:
void f() { basefield = 0; } // (1) 代码有问题,标准c++会在第一阶段查找时给出错误。
};
template<>
class Base<bool>
{
public:
enum { basefield = 42; }
};
void g(DD<bool>& d) { d.f(); }
如果你不想在第一阶段就进行查找,那么可以在basefield前加上this->或Base<T>::,Base<T>::basefield的名字是依赖型的(第一阶段时,T还没定义,basefield具体是什么,依赖于T,直到实例化时才可以知道T是什么),C++标准规定“对于依赖型的名字,会推迟到实例化的时候查找”。查找推迟后,错误是还有,不过具体错误已经清晰定位了。
c++模板的确是个挺复杂的东东,就算你不写模板代码,也免不了要跟模板打交道,比如使用stl。编写或使用模板时要注意一些规则:
1.3.1. 派生类如果使用基类的函数或成员变量,应该在前面加“this->”,或在类声明中加入“using 基类函数;”
比如:
template<typename CharType>
class CMyString : public std::basic_string<CharType>
{
public:
size_t Length()
{
returnthis->length(); // 这里会调用std::basic_string<CharType>::length()函数。
}
};
或
template<typename CharType>
class CMyString : public std::basic_string<CharType>
{
using std::basic_string<CharType>::length; // 声明使用基类的length函数。
public:
size_t Length()
{
// 有了std::basic_string<CharType>::length的声明,length()就会成为依赖型(依赖于CharType),直到实例化时才查找该函数的具体地址,如果没有using length的声明,length()会作为一个非依赖型名字,编译器会在全局函数里查找,如果找不到,则报错,如果找到,则调用全局函数length()。
return length(); // 这里会调用std::basic_string<CharType>::length()函数。
}
};
1.3.2. 使用了基类或其它模板类里定义的类型,要在类型前加typename
比如:
template<typename CharType> inline
void Fun(std::basic_string<CharType>& str)
{
// std::basic_string<CharType>::iterator 是一个名字,
// 且该名字依赖于模板类std::basic_string的参数CharType,
// 在CharType还没确定时,iterator有可能是一个类型名,也有可能是静态变量名,或是一个枚举名。
// 加上typename后,指明std::basic_string<CharType>::iterator是一个类型名。
typename std::basic_string<CharType>::iterator it = str.begin();
}
1.4. 标准与现实之间
当然,对于以上这些标准vc和xlc执行得不是太严格,不按照上面这样用也可以通过,但gcc是严格按照标准办事的。如果代码想要有比较强的移植性以及健壮性的话,我们自己还是按照标准写为好。
2. 操作系统
2.1. API
这个大家应该会比较清楚。
写程序免不了要使用操作系统的功能,windows与linux、unix定义了不同的API用来访问操作系统。
如果程序必须操作系统的特定功能的话,那就没得说了(不过这样的程序在设计之初就没打算过要跨平台)。
如果程序使用的是一些操作系统的公共功能的话,那么使用以下形式的代码
#ifdef WIN32
调用windows的API
#elseifdef LINUX
调用linux的API
#else
调用XXX系统的API
#endif
编译的时候,只需要把相关平台上加上这些宏就会自动调用自身平台的API了。
2.2. 硬件 32位 64位
目前windows的硬件是以32位的intel处理器为主,而大多数unix是64位的专用处理器。
对于long以及指针类型来说,在windows平台是32位,而在aix可能就是64位了。VC是使用__int64来作为64位类型的,windows上还大量使用了一个叫LARGEINT的结构体来作为64位变量的储存:
typedefstruct _LARGEINT
{
ULONG LowLong;
LONG HighLong;
} LARGEINT;
typedef LARGE_INTEGER *LPLARGEINT;
3. 编译器对语言的扩展
3.1. 编译器命令与选项
因为本文主要讨论代码的可移植性方面的,由于各编译器命令和选项繁多,不一一列出了。
3.2. 函数的调用约定:
调用约定 |
语义 |
cdecl |
从右到左地将被调用函数的参数压入堆栈。在执行完毕之后,由调用函数将参数弹出堆栈。 |
stdcall |
从右到左地将被调用函数的参数压入堆栈。在执行完毕之后,由调用函数将参数弹出堆栈。 |
fastca |
将最前面的两个参数传递到 ECX 和 EDX 寄存器中,同时将所有其他参数从右到左地压入堆栈。由被调用函数负责清除执行后的堆栈。 |
为了支持这些函数属性,比如stdcall,VC中是在函数前加“__stdcall”或“_stdcall”,而gcc是函数前加“__attribute__((stdcall))”。xlc是两种方法都支持。
3.3. dll函数符号导出
VC使用“__declspec(dllexport)”导出函数符号,使用“__declspec(dllimport)”导入。但为了得到纯C(名字完全没有修改过)函数,建议使用.def文件导出。
gcc使用__attribute__ ((visibility("hidden")))声明为不导出,使用((visibility("default")))声明为导出。如果没有声明,则默认为导出。但因为大多数函数是不导出的,建议在编译时加上-fvisibility=hidden选项,这样,只有没有做声明或声明为hidden的都不导出,这样可以加快dll的加载速度。
xlc也是默认为导出。
3.4. 宽字符
c++为宽字符制定了标准类型wchar_t,但有些编译器是以扩展的形式提供该类型的。比如VC,你要#include<wchar.h>才能使用wchar_t,而gcc是把wchar_t作为内建类型。
注意,VC提供了选项是否把wchar_t作为内建类型,但使用它之前,最好你的工程中用到的其它静态库都是使用该选项的,要不然就会链接不过。
4. Unicode,使用icu
关于unicode本身就是一个很复杂的问题,这里不作专门讨论了。
c++代码的字符类型分为单字节和宽字节,经常需要做字符编码间的转换。之前做一个xml读写的项目,需要utf16和gb2312格式间的互转,但系统自带的函数和xerser-c带的函数在windows上是可以正常工作的,在aix上却不可以转换中文字符。找了些资料,IBM的icu(International Component for Unicode)组件可以解决这个问题。
下载icu-4代码,编译后得到icuuc库组件和一个叫“include”的目录(里面是头文件)。把包含路径设到它的“include”,以及链接icuuc后,以下代码可以在多个平台上实现utf16和gb2312的转换。
// XMLString自带的转换函数在aix上不能把utf16转成gb2312。
// 改为使用icu的转换函数。
// {{
#ifndef UCNV_H
#include "unicode/ucnv.h"
#endif
inline
char* UTF16_to_gb2312(constwchar_t* strtranscode, size_type len)
{
const size_type sizeDest = (len + 1) * 2;
char* pResult = newchar[sizeDest];
UErrorCode status = U_ZERO_ERROR;
UConverter *conv = ucnv_open("gb2312", &status);
assert(U_SUCCESS(status));
ucnv_fromUChars(conv,
pResult, sizeDest,
(const UChar*)strtranscode, len,
&status);
ucnv_close(conv);
return pResult;
}
inline
wchar_t* gb2312_to_UTF16(constchar* strtranscode, size_type len)
{
const size_type sizeDest = (len + 1);
wchar_t* pResult = newwchar_t[sizeDest];
UErrorCode status = U_ZERO_ERROR;
UConverter *conv = ucnv_open("gb2312", &status);
assert(U_SUCCESS(status));
ucnv_toUChars(conv,
(UChar*)pResult, sizeDest,
strtranscode, len,
&status);
ucnv_close(conv);
return pResult;
}
// }}
5. 封装
对于编写跨平台的代码当然好,但为了跨平台,少不了”#ifdef WIN32”之类的代码,这种代码一但多了,会很影响阅读的。应该把这些公共功能包装起来,提供一个可以被多个平台访问的接口,特别是对于API的封装。
5.1.1. ACE
现在有很多开源的类库有API的封装,比如ACE等。但只是实现了轻量级的封装,大概是线程、进程、互斥体、锁等。ACE虽然是对系统作了个轻量级封装,但其本身却是一个重量级的库。
5.1.2. QT
API中最复杂的是窗体和图形设备相关的,Windows的窗体和图形设备API加起至少有几千个。实现这些封装可以称得上是重量级的了,比如QT,但QT是一种比较特殊的开源,不给钱而使用它开发营利性软件会引起连锁性的法律问题。
Kylix(delphi的linux版)上所使用的支持跨平台的CLX组件库就是基于QT的。Kylix支持pascal和c++。不过用c++就比较转折了,先是用c++调用pascal写的VCL,再用VCL调用c++写的CLX,CLX再调用QT。如果刚好你程序在这段出现问题想调试一下代码,就头大了。(有空打算一篇写关于跨语言的文章)
相关推荐
在探讨将Win32 C/C++应用程序移植到Linux上时,我们需要注意两个主要操作系统间在进程、线程以及共享内存服务方面的差异。以下详细说明了这些方面的API映射及相关知识点。 首先,关于初始化和终止。在Win2K/NT环境...
Linux Windows下 C_C++开发的差异 平台差异简介: Windows 和 Unix 是当前两大主流操作系统平台,基于 C/C++ 的开发人员经常会面临这两个平台之间的移植的问题。 Unix 作为一个开发式的系统,其下有出现了很多分支...
在Linux和AIX这样的Unix-like操作系统中,cximage库的移植性和兼容性尤为重要。由于这两个系统都是命令行驱动,开发者通常需要在终端下进行编译和运行程序。cximage库提供跨平台接口,使得在这些系统上实现图像处理...
总之,《Unix/Linux下C/C++开发技术》一文旨在帮助开发者理解和应对跨平台开发中的挑战,特别是在Unix/Linux与Windows平台间进行代码移植时所遇到的问题。通过深入了解语言特性、编译器行为以及开发流程中的最佳实践...
在Windows平台上安装ACE需要进行以下步骤: 1. 将程序包放在D盘根目录下面,目录结构如下:D:\ACE_wrappers。 2. 在目录D:\ACE_wrappers/ace/下面建立一个config.h文件,如果已经存在,则只需要修改内容。 3. 在...
- **广泛的硬件支持**:Linux最初是为Intel x86架构编写的,但随着时间的发展,它已经成功移植到了各种各样的硬件平台上,这使得Linux成为了一个极其灵活和可移植的操作系统。 - **广泛应用**:目前超过90%的超级...
在不同平台的移植环境中,例如LINUX上使用gcc编译器,而HP-UX和AIX平台则分别使用HPC/aC++和XLC/C++编译器。在硬件配置方面,不同平台提供了不同的CPU架构和时钟频率。在移植过程中,针对不同平台的硬件特性,需要...
本案例主要涉及`snmp++v3.2.23`在AIX 5操作系统下的编译过程,这是一个针对网络管理的SNMP(简单网络管理协议)C++库。AIX,全称为Advanced Interactive eXecutive,是IBM推出的一种Unix操作系统,它有着自己的构建...
POCO库支持多种桌面/服务器操作系统,如Windows、Linux、Mac OS X、Solaris、HP-UX、AIX,以及嵌入式系统,如Windows Embedded CE、Embedded Linux、iOS、QNX、VxWorks和Android。这体现了POCO库的跨平台兼容性。 ...
2. **移植性评估**:工具会评估哪些部分代码可以直接在K-UX上运行,哪些部分需要修改,以及可能遇到的兼容性问题。这涉及到C++标准库、线程库、网络库等的对比分析。 3. **代码转换**:工具可能会自动或半自动地将...
- **GNU编译工具GCC**:介绍如何使用GCC来编译C/C++等语言的源代码,包括编译选项、预处理过程等。 - **内存管理**:讲解Unix/Linux下的内存分配机制、内存泄漏检测方法以及如何高效管理内存资源。 - **文件I/O**:...
PRDC: 服务框架抽象层,面向对象的调度框架(支持:WINDOWS,AIX,Solaris,LINUX). MDB: 内存数据库内核,支持DBMS自动加载,多机容错,分布式支持,容量无 极限. 支持的功能: 1.分布式应用框架。 2.支持主流的操作...
3. libJNativeCpp.so:这是Unix-like系统下的动态链接库,与JNativeCpp.dll类似,提供JNative在Linux、AIX或HP-UX等系统上与C++库交互的能力。 通过这些文件,开发者可以在Java应用程序中利用JNative库,无论是...
支持 AIX、BSDi、FreeBSD、HP-UX、Linux、Mac OS、Novell Netware、NetBSD、OpenBSD、OS/2 Wrap、Solaris、SunOS、Windows 等多种操作系统。 为多种编程语言提供了 API。这些编程语言包括 C、C++、C#、Delphi、...
2.支持AIX、FreeBSD、HP-UX、Linux、Mac OS、NovellNetware、OpenBSD、OS/2 Wrap、Solaris、Windows等多种操作系统 3.为多种编程语言提供了API。这些编程语言包括C、C++、Python、Java、Perl、PHP、Eiffel、Ruby和...
总之,"polred.rar"文件包含的内容可能会涵盖如何在Windows平台上进行软件开发,以及如何在Unix和Linux系统中编写程序。无论是初学者还是经验丰富的开发者,都能从中学习到关于操作系统交互、文件处理、系统调用等...
GCC(GNU Compiler Collection)是GNU编译器集合,它支持多种编程语言,如C、C++、Objective-C、Fortran、Ada、Java、Go等,并且能够在多种不同的硬件平台上进行编译。GCC是Unix/Linux环境下广泛使用的编译工具之一...
### Linux基本知识及与Windows的区别 #### Unix/Linux操作系统的发展历程 - **Unix的起源:** Unix操作系统最初由美国贝尔实验室的肯·汤普森在1969年开发。最初,Unix是用汇编语言编写的,后来在1972年由丹尼斯·...