1、c/c++程序如何实现跨平台
c/c++属于典型的编译型语言,跨平台时大致分两种情况。
第一种:跨平台时不需要修改源码
直接换一个针对另一个环境的编译器来重新编译即可。
第二种:需要修改源代码
先修改源代码,然后再换编译器实现重新编译。
(a)第一种:不需要修改源码,直接换一个环境的编译器重新编译
什么样的C/C++程序跨平台时,不需要修改源码
只要程序不包含“平台差异代码”,包含的都是“通用代码”的话,跨平台时,不需要修改源码。
疑问:什么样的代码是“通用代码”?
如果代码是如下两种情况的话,就是通用代码。
- 代码只涉及c/c++基本语法
只要不同环境支持该语言,那么基本语法都是支持的。
- 当代码调用了库函数时,只要该库是不同平台都支持的通用库的话,调用库函数的代码也是通用代码
比如C标准库就是一个通用库,windows、Linux、Unix等OS都支持,所以在程序中调用c标准库的printf函数时,不管是windows、Linux、unix都支持,所以跨平台时printf函数不需要修改。
例子
#include <stdio.h> int main(void) { int a = 100; /* 只与基本语法归相关的代码 */ while(1) { a = a + 10; if(a==100) break; } printf("a = %d\n", a); //不同环境都支持的通用库函数接口 return 0; }
以上这个是通用代码,跨平台时不管是在windows下、还是在Linux下运行,这个通用代码时不需要修改源码的,换编译器重新编译即可。
由于C标准库几乎被大多数的OS支持,所以C程序中有printf、scanf、malloc等时,不管在什么平台下都能用,跨平台时不需要修改。
如果平台不支持你要的库,但是你还想用,你就必须自己来搞定这个库,有两种搞定方法,
第一种:在该环境下安装对应的库
第二种:直接将库和“可执行程序”放在一起,发布程序时一起发布
(b)第二种:需要修改源码
跨平台时为了减少麻烦,我们建议在程序中最好尽量只写通用代码,如此一来跨平台时源码就不用修改了。
但是问题是,有些时候只能做到80%~90%是通用代码,程序中有10%~20%的与平台相关的代码。
比如因为某些特殊原因,C程序中需要直接调用OS API,但是不同的OS的OS API又有区别,与OS API相关的代码就是典型的平台相关的代码。
这里以windows、Linux为例,为了让我们的C/C++程序能够很好的面对windows、Linux,有如下两种解决办法:
第一种:写两份独立的功能完全相同的程序,一份专门针对Windows,另一分专门针对Linux
第二种:只写一份代码,使用条件编译来处理平台相关的代码。
· 写两份独立的C程序,一个针对Windows,另一个针对Linux
- 在windows下运行的C程序
#include <windows.h> //windows OS API所需的头文件 int main(void) { /* 通用代码 */ int i = 0; while(1) { if(i>100) break; else i++; } /* 平台相关代码:windows的操作文件的OS API */ HANDLE hfile = CreateFile(".\file", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE \ | FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL); int dwRead = 0; WriteFile(hfile, &i, sizeof(i), &dwRead, 0); //将i写到file中 return 0; }
windows环境编译器
windows的C程序 ————————————————> 在windows下的可执行程序
- 在Linux下运行的C程序
/* Linux的OS API所需的头文件 */ #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> int main(void) { /* 通用代码 */ int i = 0; while(1) { if(i>100) break; else i++; } /* 平台相关代码:Linux的操作文件的OS API */ int fd = open("./file", O_RDWR | O_CREAT, 0774); write(fd, &i, sizeof(i)); //将i写到file return 0; }
Linux环境编译器
Linux的C程序 ————————————————> 在Linux下的可执行程序
· 这种方式的缺点
由于%80~%90都是相同的通用代码,仅为了那一点平台相关代码的不同,就要写两份独立的程序,显然是不是很合适,最起码很浪费时间,如果写一份就能搞定的话,这是最好的。
不过如果80%的代码都是平台相关代码,只有20%是通用代码的话,此时写两份完全独立的程序其实更划算。
· 只写一份代码,使用条件编译来兼容。
这里举一个非常简单的例子,这个例子不具有实用性,但是确实能够说明说明“条件编译”对于跨平台的重要性。
#define WINDOWS #ifdef WINDOWS # include <windows.h> #elif defined LINUX # include <sys/types.h> # include <sys/stat.h> # include <unistd.h> # include <fcntl.h> #endif int main(void) { /* 通用代码 */ int i = 0; while(1) { if(i>100) break; else i++; } #ifdef WINDOWS /* 平台相关代码:windows的操作文件的OS API */ HANDLE hfile = CreateFileA(".\file", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE \ | FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL); int dwRead = 0; WrieFile(hfile, &i, sizeof(i), &dwRead, 0); //将i写到file中 #elif defined LINUX /* 平台相关代码:Linux的操作文件的OS API */ int fd = open("./file", O_RDWR | O_CREAT, 0774); write(fd, &i, sizeof(i)); //将i写到file中 #endif return 0; }
- 修改代码中的宏,然后通过“条件编译”来保留对应平台的代码
- 使用对应平台的编译器来重新编译
在真实开发中修改源码时,其实不仅仅只会打开和关闭条件编译,有时还需要修改代码中其它相关数据。
2、java有条件编译吗,java程序跨平台时需要修改源码吗?
前面说过,只要环境安装了java虚拟机,java只需要一次编译即可到处运行,通过“只需要编译一次”的这句话我们就能感觉到,java程序跨平台时,其实不需要修改源码。
为什么java程序跨平台时不需要改源码?
因为在java程序的代码中涉及的通用代码,没有平台相关的代码,跨平台完全由“虚拟机”来完成,总之java被设计为了一种跨平台性非常好的语言。
正是由于java是一个跨平台性非常好的语言,所以当初借鉴C来设计java时,设计者果断去掉了C中“条件编译”,总之这里就是想告诉大家,java是没有条件编译这个玩意的,不过有类似可以模拟“C条件编译”东西。
3、再举一个跨平台的例子 —— 跨芯片
我们前面举了跨OS这个平台的例子,我们现在举一个跨“芯片”这个平台的例子,那么我们举什么例子呢?
我们这里举一个ST(意法半导体)的STM32标准库的例子,标准库为了同时支持STM32F427_437xx、STM32F429_439xx、STM32F40_41xx等系列芯片,大量的用到了条件编译。
之所以让库同时支持这么多系列的芯片,主要是为了减少开发成本,因为如果针对每个系列的芯片都开发一个库的话,ST开发标准库的工程师非吐血不可,所以具有极高相似性的系列芯片,都使用同一个库,肯定是明智之举。
库中80%的是通用代码,剩余20%则为与“不同系列芯片”相关的差异性代码,这些差异性代码则通过“条件编译”来选择,我们这里举一个库函数例子:
void SystemCoreClockUpdate(void) { /* 通用代码 */ /* Get SYSCLK source ----*/ tmp = RCC->CFGR & RCC_CFGR_SWS; switch (tmp) { case 0x00: /* HSI used as system clock source */ SystemCoreClock = HSI_VALUE; break; case 0x04: /* HSE used as system clock source */ SystemCoreClock = HSE_VALUE; break; case 0x08: /* PLL P used as system clock source */ pllsource = (RCC->PLLCFGR & RCC_PLLCFGR_PLLSRC) >> 22; pllm = RCC->PLLCFGR & RCC_PLLCFGR_PLLM; /*————————————————差异性代码———————————————— */ //定义了STM32F40_41xxx或者STM32F427_437xx或者STM32F429_439xx等宏时,代码有效 #if defined(STM32F40_41xxx) || defined(STM32F427_437xx) || defined(STM32F429_439xx) || \ defined(STM32F401xx) || defined(STM32F412xG) || defined(STM32F413_423xx) || \ defined(STM32F446xx) || defined(STM32F469_479xx) if (pllsource != 0) { /* HSE used as PLL clock source */ pllvco = (HSE_VALUE / pllm) * ((RCC->PLLCFGR & RCC_PLLCFGR_PLLN) >> 6); } else { /* HSI used as PLL clock source */ pllvco = (HSI_VALUE / pllm) * ((RCC->PLLCFGR & RCC_PLLCFGR_PLLN) >> 6); } //否则如果定义了STM32F410xx或者STM32F411xE宏的话,以下代码有效 #elif defined(STM32F410xx) || defined(STM32F411xE) if (pllsource != 0) { /* HSE used as PLL clock source */ pllvco = (HSE_BYPASS_INPUT_FREQUENCY / pllm) * ((RCC->PLLCFGR & RCC_PLLCFGR_PLLN) >> 6); } //同理 #endif /* STM32F40_41xxx || STM32F427_437xx || STM32F429_439xx || STM32F401xx || STM32F412xG || STM32F413_423xx || STM32F446xx || STM32F469_479xx */ pllp = (((RCC->PLLCFGR & RCC_PLLCFGR_PLLP) >>16) + 1 ) *2; SystemCoreClock = pllvco/pllp; break; //同理 #if defined(STM32F412xG) || defined(STM32F413_423xx) || defined(STM32F446xx) case 0x0C: /* PLL R used as system clock source */ pllsource = (RCC->PLLCFGR & RCC_PLLCFGR_PLLSRC) >> 22; pllm = RCC->PLLCFGR & RCC_PLLCFGR_PLLM; if (pllsource != 0) { /* HSE used as PLL clock source */ pllvco = (HSE_VALUE / pllm) * ((RCC->PLLCFGR & RCC_PLLCFGR_PLLN) >> 6); } else { /* HSI used as PLL clock source */ pllvco = (HSI_VALUE / pllm) * ((RCC->PLLCFGR & RCC_PLLCFGR_PLLN) >> 6); } pllr = (((RCC->PLLCFGR & RCC_PLLCFGR_PLLR) >>28) + 1 ) *2; SystemCoreClock = pllvco/pllr; break; #endif /* STM32F412xG || STM32F413_423xx || STM32F446xx */ default: SystemCoreClock = HSI_VALUE; break; } }
在IDE中查看源码你会发现,如果条件编译成立的话,所包含的代码就是正常颜色,条件编译不成立,代码的颜色为灰色,我们阅读源码时,凡是灰色的代码统统忽略,因为这些不是我们当前需要关心的代码。
很多同学阅读带有条件编译源码时,往往不懂得阅读技巧,很喜欢全部通读,最后越看越糊涂,什么原因呢?
(1)差异定代码本来就是只能有一段代码有效,至于哪一段有效则由条件编译来选择,但是你在看的时候如果把所有差异代码联合在一起看,代码逻辑自然就是混乱的,肯定是越看越糊涂。
(2)没被条件编译选中的代码,与我们关心的平台(os/芯片)并不相干,而是其它平台的,对于其它平台我们并不是很熟悉,所以强行阅读不熟悉平台的代码,肯定非常痛苦。
所以大家看有条件编译的代码时,一定摸清门道,不然就会越看越糊涂。
为了更好的阅读含有大量条件编译的代码,建议使用专门的IDE或者想souceinsight这种源码阅读器来阅读,因为它们会自动区分颜色,方便阅读。
使用txt文本来阅读时是不颜色区分的,这时我们自己就需要清楚,哪些条件编译成立,哪些不成立,从而区分出哪些是我应该关心的代码,不过不建议使用txt文本的方式来阅读,实在是太不人性化。
有关条件编译这个玩意,还真不是一两句话就能完全讲清楚的,更多的只能等大家工作时,自己去慢慢去体会。