stdc++移植

    技术2024-06-16  66

    比较和对比编译器选项

    Visual C ++和GNU g++的cl编译器都带有几个选项。 虽然可以将cl用作独立的编译工具,但Visual C ++提供了一个简洁的集成开发环境(IDE)来设置编译器选项。 使用VisualStudio®开发的软件通常使用特定于编译器且依赖于平台的功能,这些功能可通过编译器或链接器选项进行控制。 在跨平台使用不同的编译器或工具链移植源代码时,了解编译器选项很重要。 本节探讨一些最有用的编译器选项。

    启用字符串池

    考虑以下代码片段:

    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视为本机类型。 否则,需要包括特定于实现的标头,例如windows.h或标准标头,例如wchar.h 。 g++支持本机wchar_t类型,不需要包含任何特定的标头。 请注意, wchar_t的大小因平台而异。 您可以使用-fshort-wchar g++选项将wchar_t大小强制为两个字节。

    支持C ++运行时类型识别

    如果源不使用dynamic_cast或typeid运算符,则可能会禁用运行时类型标识(RTTI)。 默认情况下,RTTI在Visual Studio 2005中处于打开状态(即/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。 使用-fno-rtti选项在g++编译此代码段将导致报告编译错误。 但是,即使没有/GR选项, cl也会编译此代码,但是生成的可执行文件在运行时崩溃。

    异常处理

    要在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! 不带/GX选项的cl : Caught! 没有-fno-exceptions g++ : Destroying A Caught! 带有-fno-exceptions g++ :编译时错误

    循环一致性

    为了实现循环一致性,请考虑清单3中的代码片段。

    清单3.用于循环一致性
    int main (int argc, char** argv ) { for (int i=0; i<5; i++); i = 7; return 0; }

    该代码不应根据ISO C ++准则进行编译,因为声明为循环一部分的i局部变量的范围仅限于循环主体,并且无法在循环外部访问。 缺省情况下, cl传递此代码没有任何错误。 但是,将/Zc:forScope选项与cl会导致编译失败。 g++行为与cl完全相反,并且为此测试产生以下错误:

    error: name lookup of 'i' changed for new ISO 'for' scoping

    若要抑制此行为,可以在编译期间使用-fno-for-scope flag 。

    使用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标准头文件中定义的数据类型,但是此文件中各个数据类型的大小(例如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变量也是如此。 这会导致在结构内部创建Kong,从而扩大内存。 清单8中的重新排列导致该结构为24个字节。

    清单8.正确的结构成员资格对齐
    struct s { int var1; int var3; long var2; char* ptr1; }; // sizeof(s) = 24 bytes

    移植多线程应用程序

    从技术上讲,线程是操作系统可以调度运行的独立指令流。 在这两种环境中,线程都存在于流程中并使用流程资源。 只要它的父进程存在并且操作系统支持它,它就有自己的独立控制流。 它可能与其他独立(或依赖)操作的线程共享进程资源,并且如果父进程死掉,它也会死掉。 这是一些典型的应用程序接口(API)的概述,可用于在Windows和UNIX环境中使项目成为多线程。 选择的接口是C运行时例程(与WIN32 API相对),以及为简化和清楚起见而符合可移植操作系统接口(POSIX)线程的例程。

    注意:由于篇幅所限,我们无法提供编写此类应用程序的其他方式的详细信息。

    创建一个线程

    Windows使用C运行时库函数中的_beginthread API。 您还可以使用其他Win32 API创建线程,但是,向前发展,您仅处理C运行时库函数。 顾名思义, _beginthread()函数创建一个执行例程的线程,其中将该例程的指针指定为第一个参数。 此例程使用__cdecl C声明调用约定并返回void。 当线程从该例程返回时,它终止。

    在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() { /* Create the mutex and reset thread count. */ mutexObject = CreateMutex( NULL, FALSE, NULL ); /* Cleared */ 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 ); /* Tell thread to die. */ 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]; /* Create independent threads each of which will execute functionC */ 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)以及特定于实现的功能(例如线程)。 本系列文章介绍了移植的各个方面。 有关此主题的更多高级信息,请参阅“ 相关主题”部分。


    翻译自: https://www.ibm.com/developerworks/aix/library/au-porting2/index.html

    Processed: 0.013, SQL: 9