最近和微软Microsoft Visual Studio 2005 开发组的一个程序员放牛娃(一般称放牛娃为牛人)讨论可移植程序的问题,我发表一下自己的观点。尽量使用标准c的类库,少使用平台提供的,注意虽然glibc提供了一个各平台的c库,但是各平台的同一版本提供的函数有很大的不同,一般难以保证你可以编译通过,即使编译通过,运行结果未必一样,opendir处理当前目录就是一个例子。尽可能使用标准c++,因为标准c++的兼容性远好于c,当然包括stl,虽然stl在vc borland c和mingw略有差异,但是可以规避的。跨平台线程编程使用boost或者intel TBB,网络编程使用ACE或者ICE。一般编程使用boost或者Loki。第一次先说明编译器的兼容性第一个例子#include <iostream>using namespace std;void main(int argc, char* argv[]){ char str1[] = "abc"; char str2[] = "abc"; const char str3[] = "abc"; const char str4[] = "abc"; const char *str5 = "abc"; const char *str6 = "abc"; char *str7 = "abc"; char *str8 = "abc"; cout << (str1 == str2) << endl; cout << (str3 == str4) << endl; cout << (str5 == str6) << endl; cout << (str7 == str8) << endl;}
注意这里用的是 #include <iostream>后面是using namespace std;不是 #include <iostream.h>, iostream.h不是标准c++的头文件,具体的原因看c++手册,我就不多说了。vc的结果是 0 0 1 1看一下vc的反编译代码,vc的反编译确实不大好用8: char str1[] = "abc";00401058 mov eax,[string "abc" (0042801c)]0040105D mov dword ptr [ebp-4],eax9: char str2[] = "abc";00401060 mov ecx,dword ptr [string "abc" (0042801c)]00401066 mov dword ptr [ebp-8],ecx10:11: const char str3[] = "abc";00401069 mov edx,dword ptr [string "abc" (0042801c)]0040106F mov dword ptr [ebp-0Ch],edx12: const char str4[] = "abc";00401072 mov eax,[string "abc" (0042801c)]00401077 mov dword ptr [ebp-10h],eax13:14: const char *str5 = "abc";0040107A mov dword ptr [ebp-14h],offset string "abc" (0042801c)15: const char *str6 = "abc";00401081 mov dword ptr [ebp-18h],offset string "abc" (0042801c)16:17: char *str7 = "abc";00401088 mov dword ptr [ebp-1Ch],offset string "abc" (0042801c)18: char *str8 = "abc";0040108F mov dword ptr [ebp-20h],offset string "abc" (0042801c)str5 str6 str7和str8使用同一个地址borland cbuilder的结果0 0 0 0,bcc32 -S 反编译的文件 char str1[] = "abc"; ; @1: mov eax,dword ptr [$mecgibia] mov dword ptr [ebp-4],eax ; ; char str2[] = "abc"; ; mov edx,dword ptr [$eicgibia] mov dword ptr [ebp-8],edx ; ; ; const char str3[] = "abc"; ; mov ecx,dword ptr [$mlcgibia] mov dword ptr [ebp-12],ecx ; ; const char str4[] = "abc"; ; mov eax,dword ptr [$epcgibia] mov dword ptr [ebp-16],eax ; ; ; const char *str5 = "abc"; ; mov ebx,offset s@ ; ; const char *str6 = "abc"; ; ?live16385@96: ; EBX = str5 mov esi,offset s@+4 ; ; ; char *str7 = "abc"; ; ?live16385@112: ; EBX = str5, ESI = str6 mov edi,offset s@+8 ; ; char *str8 = "abc"; ; ?live16385@128: ; EBX = str5, ESI = str6, EDI = str7 mov dword ptr [ebp-20],offset s@+12str1-str4使用堆栈,str5-7使用寄存器,str8使用堆栈,所以比较地址是不同的。gcc的结果0 0 1 1,我使用codeblock,不大喜欢devcpp因为这个delphi开发的c++ide确实不咋的,汇编代码0x4013ba: mov eax,ds:0x43e0000x4013bf: mov DWORD PTR [ebp-4],eax0x4013c2: mov eax,ds:0x43e0000x4013c7: mov DWORD PTR [ebp-8],eax0x4013ca: mov eax,ds:0x43e0000x4013cf: mov DWORD PTR [ebp-12],eax0x4013d2: mov eax,ds:0x43e0000x4013d7: mov DWORD PTR [ebp-16],eaxstr5-str8 全是一个地址0x4013da: mov DWORD PTR [ebp-20],0x43e0000x4013e1: mov DWORD PTR [ebp-24],0x43e0000x4013e8: mov DWORD PTR [ebp-28],0x43e0000x4013ef: mov DWORD PTR [ebp-32],0x43e000还有gcc下main函数的返回值必须int,其实这也是c++规定的,但是vc bcb 返回值为void也可以的。第二个例子 j++与++j ,j++是先用后加,++j是先加后用,#include <iostream>using namespace std;int main(int argc, char* argv[]){ int n=0,j=7; n=(++j)+(++j)+(++j); cout <<n <<endl;}如果n=(j++)+(j++)+(j++);大家没有意见是21,可以n=(++j)+(++j)+(++j);呢vc的结果是289: int n=0,j=7;00401798 mov dword ptr [ebp-4],00040179F mov dword ptr [ebp-8],710: n=(++j)+(++j)+(++j);004017A6 mov eax,dword ptr [ebp-8]004017A9 add eax,1004017AC mov dword ptr [ebp-8],eax004017AF mov ecx,dword ptr [ebp-8]004017B2 add ecx,1004017B5 mov dword ptr [ebp-8],ecx004017B8 mov edx,dword ptr [ebp-8]004017BB add edx,dword ptr [ebp-8]004017BE mov eax,dword ptr [ebp-8]004017C1 add eax,1004017C4 mov dword ptr [ebp-8],eax004017C7 add edx,dword ptr [ebp-8]004017CA mov dword ptr [ebp-4],edxvc是取出[ebp-8]的值7,放到eax,然后加1,因为内存地址不允许直接加,然后把eax放回[ebp-8],然后取出放到ecx,然后加1,再放回[ebp-8],此时值为9,然后取出放到edx,然后加[ebp-8]的值9,这是edx是18,然后取出[ebp-8]的值9,放到eax,加1在放回[ebp-8]这是[ebp-8]的值是10,然后加上edx的值,这个结果就是28.gcc的结果28 0x4013ba: mov DWORD PTR [ebp-4],0x00x4013c1: mov DWORD PTR [ebp-8],0x70x4013c8: lea eax,[ebp-8]0x4013cb: inc DWORD PTR [eax]0x4013cd: lea eax,[ebp-8]0x4013d0: inc DWORD PTR [eax]0x4013d2: mov eax,DWORD PTR [ebp-8]0x4013d5: mov edx,DWORD PTR [ebp-8]0x4013d8: add edx,eax0x4013da: lea eax,[ebp-8]0x4013dd: inc DWORD PTR [eax]0x4013df: mov eax,edx0x4013e1: add eax,DWORD PTR [ebp-8]0x4013e4: mov DWORD PTR [ebp-4],eax0x4013e7: mov eax,DWORD PTR [ebp-4][ebp-8]的地址给eax,然后直接加1,这是值是8,然后在加1 ,值变为9,然后[ebp-8]放到eax,edx然后相加,edx的值为18,然后[ebp-8]的值加1变为10,然后和edx相加,edx的值是28,然后edx给eax,用于输出。borlandc的结果是30 mov eax,7 ; ; n=(++j)+(++j)+(++j); ; ?live16385@32: ; EAX = j inc eax inc eax inc eax lea edx,dword ptr [eax+eax] add eax,edx ; ; cout <<n <<endl; ; ?live16385@48: ; EAX = n道理很简单,borland的程序员比较懒,先把所有的值加完了,再计算。第3个例子 for(int i=0;i<3;i++) printf("%d\n",i); i=9; printf("%d\n",i);学过c的程序员,认识后面的i=9不能编译通过,但是vc6是可以的。vc的汇编代码9: for(int i=0;i<3;i++)00401268 mov dword ptr [ebp-4],00040126F jmp main+2Ah (0040127a)00401271 mov eax,dword ptr [ebp-4]00401274 add eax,100401277 mov dword ptr [ebp-4],eax0040127A cmp dword ptr [ebp-4],30040127E jge main+43h (00401293)10: printf("%d\n",i);00401280 mov ecx,dword ptr [ebp-4]00401283 push ecx00401284 push offset string "%d\n" (0043101c)00401289 call printf (00401550)0040128E add esp,800401291 jmp main+21h (00401271)11: i=9;00401293 mov dword ptr [ebp-4],912: printf("%d\n",i);0040129A mov edx,dword ptr [ebp-4]0040129D push edx0040129E push offset string "%d\n" (0043101c)004012A3 call printf (00401550)004012A8 add esp,8放在[ebp-4]中,当然可以的。for(int i=0;i<3;i++) printf("%d\n",i);for(int i=9;i<12;i++) printf("%d\n",i);是正确地,vc编译出现error C2374: 'i' : redefinition; multiple initialization。最后一个例子 int n=4; int a[n]; for(int i=0;i<n;i++) { a[i]=n; }学过谭浩强的c的人认为他是错的,但是在gcc下他是对的,其实很好理解,int a[n],是在栈上分配的空间,真不理解c的创始人和c标准委员会的人如何想的。 |