首页运维零件 › Glibc包含了主要的C库,gcc是可以在多种硬体平台上编译出可执行程序的超级编译器

Glibc包含了主要的C库,gcc是可以在多种硬体平台上编译出可执行程序的超级编译器

本文首先提出平台相关代码造成的两个问题,然后针对这两个问题循序渐进依次提出解决方案,在分析了前两个方案弱点的基础上,最后着重介绍一种基于多种设计模式的
Linux 平台相关代码的解决方案,并给出此方案的 C++ 实现。

 
我相信学过C语言的同学,都会在书中看到C语言特点一定有:可移植性。但是什么是可移植?如何才能可移植?C语言是如何做到可移植的?对于初学者,可移植可能是一个经常遇到却很神秘的词。我想通过这篇文章来表达我对于可移植性的一些想法。

一.函数库的区别

linux下的C函数库和windows下的函数库系统调用的机制不一样,Glibc包含了主要的C库。
这个库提供了基本例程,用于分配内存、搜索目录、打开关闭文件、读写文件、字串处理、模式匹配、数学计算等等。

所说的机制不一样不单是指中断号的问题,中断号也是通过input参数和output把函数地址和输出地址定位在寄存器的,那些函数在windows和linux下的实现应该是不一样的,就拿文件系统来说,ext3和fat32的怎么可能一样.还有mm内存管理,都是不一样的.中断还是属于硬件层的,X86上的应该都差不多,但操作系统层的实现就大不相同了。

windows C库格式为  .dll( 动态链接库英文为DLL,是Dynamic Link Library
的缩写形式,DLL是一个包含可由多个程序同时使用的代码和数据的库,DLL不是可执行文件)。
生成的*.exe 。

linux C 库格式为  .so(.so 为共享库 : 动态库(shared library/shared
object/dynamic link library)。生成*elf(Linux ELF ELF = Executable and
Linkable
Format),可执行连接格式,是UNIX系统实验室(USL)作为应用程序二进制接口(Application
Binary Interface,ABI)而开发和发布的。扩展名为elf)。

标准 C
库函数以及数据类型等在任何操作系统下都可以编译运行,并且效果是完全一样的,但其内部的实现原理及存储方式不一定一样。这些库函数实际上在不同系统操作系统上的实现就是依赖于操作系统本身的
API。Linux 上当然就是 Linux 的 API,而 Windows 则是 Windows
API。就像最常见的 fopen 函数,在 Windows 上他是通过 CreateFile
函数实现的,而 Linux 上则是通过 open 函数。API 说白了就是函数,通常说
API 指的是操作系统函数。Linux 的 shell 相当于 Windows
的命令行,它只不过是一个解析命令和执行程序的环境。这个环境其实也是一个独立的程序。任何平台的程序要运行,就得依赖于该操作系统的
API。

系统调用是应用程序和操作系统内核之间的功能接口。其主要目的是使得用户可以使用操作系统提供的有关设备管理、输入/输入系统、文件系统和进程控制、通信以及存储管理等方面的功能,而不必了解系统程序的内部结构和有关硬件细节,从而起到减轻用户负担和保护系统以及提高资源利用率的作用。

Linux 平台相关代码带来的问题

目前市场上存在着许多不同的 Linux 平台(例如:RedHat, Ubuntu, Suse
等),各大厂商和社区都在针对自己支持的平台进行优化,为使用者带来诸多方便的同时也对软件研发人员在进行编码时带来不少问题:

  1. 由于程序中不可避免的存在平台相关代码(系统调用等),软件研发人员为了保证自己的产品在各个
    Linux
    平台上运行顺畅,一般都需要在源代码中大量使用预编译参数,这样会大大降低程序的可读性和可维护性。
  2. 接口平台无关性的原则是研发人员必须遵循的准则。但是在处理平台相关代码时如果处理不当,此原则很有可能被破坏,导致不良的编码风格,影响代码的扩展和维护。

本文将针对这两个问题循序渐进依次提出解决方案。

   
首先,在这里铺垫一下。学过Win32程序设计的人肯定都听说过APIApplication
Program
Interface)。我就先说说API,高手绕过。API对于程序员来说就是系统提供的接口,任何涉及系统调用都要通过API来完成。对于不同的操作系统都有不同的一套API,也就是说对于不同的操作系统系统调用的接口是完全不同的。所以在API层我们是不能移植的。

 

二.其他区别

1)系统平台不一样        
底层开发就涉及到了系统内核的问题,对于linux来说,你可以知道它里面是什么结构,而windows。。。

2)编译器环境不一样
linux采用gcc编译器,gdb调试工具,和多种可视化的编辑器如
emacs,kedit等等,也有文本的vi/vim,GDB的功能是非常强大的,个人认为较win下的好。尽管win下的mingw,devcpp集成了gcc,但是搞的总让人不爽~,gcc对标准的支持是相当的好。

3)针对人群不一样
win下主要还是面向商业化的开发,而绝大多数的编程爱好者则喜欢属于自己开阔自由的系统下编程,不愿意禁锢在windows下(MS)的包围中。

4)发展方向不一样。
OpenSource的思想已经在linux这片净土开花(参见GPL....)。
找资料方便,源代码公开,可以体验开发的乐趣。
win下,ms逐步把一批开发人员束缚在它自己的系统里面,开发环境越来越傻瓜,这能不能叫人性化呢?搞到最后。。。可能越走越远~

5)版权问题
win下的好多好多东西都涉及到版权问题,linux的free软件虽然是自由软件,不过好多好多都是免费用于商业化的。。。当然有的需要开放源代码,好多也不需要。C++的区别也差不多

Linux操作系统作为自由软件的代表,它优良的性能使得它的应用日益广泛,不仅得到专业人士的肯定,而且商业化的应用也是如火如荼。在Linux中,大部分的系统调用包含在Linux的libc库中,通过标准的C函数调用方法可以调用这些系统调用。那么,对Linux的发烧友来说,如何在Linux中增加新的系统调用呢?

通过设置预编译选项来处理平台相关代码

通过为每个平台设置相关的预编译宏能够解决 Linux
平台相关代码的问题,实际情况下,很多软件开发人员也乐于单独使用这种方法来解决问题。

假设现有一动态库 Results.so,SomeFunction()
是该库的一个导出函数,该库同时为 Rhel,Suse,Ubuntu 等三个平台的 Linux
上层程序服务。(后文例子均基于此例并予以扩展。)

      
下面我就来说说可移植,可移植顾名思义就是可以从一个平台移植到另外一个平台,但是大家一定要清楚,移植是基于操作系统的。但是这个时候,我们需要注意一点:基于各种操作系统平台不同,应用程序在二级制级别是不能直接移植的。我们只能在代码层去思考可移植问题,在API层面上由于各个操作系统的命名规范、系统调用等自身原因,在API层面上实现可移植也是不大可能的。那怎么才能实现可移植呢?
      
我们首先来看看现在主流的Windows和Linux平台下代码可移植性。有什么办法解决这个问题呢?答案是:在各个平台之间,基于大部分需求抽象出一个中间层。在中间层中,中间层用了屏蔽底层细节,在我们程序员看来C言语库就是这样一个中间层的作用。在各个平台下,我们默认C标准库中的函数都是一样的,这样基本可以实现可移植。但是对于C库本身而言,在各种操作系统平台下其内部实现是完全不同的,也就是说C库封装了操作系统API在其内部的实现细节。
      
因此,C语言提供了我们在代码级的可移植性,即这种可移植是通过C语言这个中间层来完成的。
      
当然,大家都可以看出上面的可移植是有条件的,C语言本身不能实现完全的可移植,为什么呢?因为,在我们程序中,我们经常会调用系统API,由于这些API在C语言中没有对其封装,所以我们只能用使用其原始的API,对于原始的API在各个操作系统中他们命名不同,就不能跨平台移植。所以,我们要写出完完全全的跨平台的程序,还是需要其他的一些手段。例如在我们的代码中下功夫。以下代码可以帮助我们实现各平台之间的可移植:
#ifndef _WINDOWS_        CreateThread();      //windows下线程的创建
#else        Pthread_create();    //Linux下线程的创建 #endif
对于头文件,也使用同样的预编译宏来实现。如: #ifndef _WINDOWS_       
#include <windows.h> #else        #include <thread.h>
#endif
这样就可以实现代码的可移植了。在编译的时候只要通过#define就可以选择在那个平台下完成程序的编译。
      
综上所述,我们都是将C,C++等各种语言当作中间层,以实现其一定程度上的可移植。如今,语言的跨平台的程序都是以这样的方式实现的。但是在不同的平台下,仍需要重新编译。  

1、gcc(gnu collect
compiler)是一组编译工具的总称。它主要完成的工作任务是“预处理”和“编译”,以及提供了与编译器紧密相关的运行库的支持,如libgcc_s.so、libstdc++.so等。

三.总结

Linux 简单来说跟 Windows
一样是一种操作系统,只是两者之间的使用方法和习惯有所区别。具体的区别其实很大,不过已经不是能在这里说清楚的事情了。

对于 C
编程来说,他们最大的区别就是提供给你的系统相关的特性不一样。这个导致了所谓的移植性问题以及平台特性的问题。形象点的比喻是你可以认为
Linux 和 Windows
都是人。他们都懂一部分英语,但是一个的母语是汉语,一个是德语。你用的英语如果他们刚好都懂,那他们就能很好的帮你做事。如果你说的英语他们不懂,那就只能用他们的母语给他们说。这样的结果就是你用德语说的
Linux 听不懂;你用汉语说的 Windows
又听不懂。所以,如果是简单的编程,那他们没有多大区别。如果是复杂的...你就准备翻译一方的母语成另一方的母语吧。

由于版权原因,库函数的源代码一般是不可见的,但在头文件中你可以看到它对外的接口。
库函数是人家写的程序,你拿来用在你的程序里.
首先标准只是规定了这些函数的接口和具体的运行效率的要求,这些函数具体是怎么写得要看各个编译器的实现和平台。

如果你用的是visual
studio的话,微软提供了一部分C运行时(CRT)的源码,里面会有memcpy,strcpy之类的函数的实现,我的visual
studio 2005下的路径是C:\Program Files\Microsoft Visual Studio
8\VC\crt\src,你可以对比参照一下。

其他差异详见:《Linux/Windows下
C/C++开发的差异zz》

1 Linux系统调用机制

清单 1. 设置预编译选项示例代码如下:
// Procedure.cpp 
 void SomeFunction() 
 { 
    //Common code for all linux 
    ...... 
    ...... 
 #ifdef RHEL 
    SpecialCaseForRHEL(); 
 #endif 

 #ifdef SUSE 
    SpecialCaseForSUSE(); 
 #endif 

 #ifdef UBUNTU 
    SpecialCaseForUBUNTU(); 
 #endif 

    //Common code for all linux 
    ...... 
    ...... 

 #ifdef RHEL 
    SpecialCase2ForRHEL(); 
 #endif 

 #ifdef SUSE 
    SpecialCase2ForSUSE(); 
 #endif 

 #ifdef UBUNTU 
    SpecialCase2ForUBUNTU(); 
 #endif 

    //Common code for all linux 
 ...... 
 ...... 
 }

开发人员可以通过设置 makefile 宏参数或者直接设置 gcc
参数来控制实际编译内容。

例如:

gcc -D RHEL Procedure.cpp -o Result.so -lstdc++   // Use RHEL marco

SpecialCaseForRHEL(),SpecialCaseForSUSE(),SpecialCaseForUBUNTU()
分别在该库 (Results.so) 的其他文件中予以实现。

本文出自 “HelloWorld”
博客,请务必保留此出处

Linux系统下的Gcc(GNU C
Compiler)是GNU推出的功能强大、性能优越的多平台编译器,是GNU的代表作品之一。gcc是可以在多种硬体平台上编译出可执行程序的超级编译器,其执行效率与一般的编译器相比平均效率要高20%~30%。 
Gcc
编译器能将C、C++语言源程序、汇程式化序和目标程序编译、连接成可执行文件,如果没有给出可执行文件的名字,gcc将生成一个名为a.out的文件。
在Linux系统中,可执行文件没有统一的后缀,系统从文件的属性来区分可执行文件和不可执行文件。而gcc则通过后缀来区别输入文件的类别,下面我们来
介绍gcc所遵循的部分约定规则。 
.c为后缀的文件,C语言源代码文件; 
.a为后缀的文件,是由目标文件构成的档案库文件; 
.C,.cc或.cxx 为后缀的文件,是C++源代码文件; 
.h为后缀的文件,是程序所包含的头文件; 
.i 为后缀的文件,是已经预处理过的C源代码文件; 
.ii为后缀的文件,是已经预处理过的C++源代码文件; 
.m为后缀的文件,是Objective-C源代码文件; 
.o为后缀的文件,是编译后的目标文件; 
.s为后缀的文件,是汇编语言源代码文件; 
.S为后缀的文件,是经过预编译的汇编语言源代码文件。 
Gcc的执行过程 
虽然我们称Gcc是C语言的编译器,但使用gcc由C语言源代码文件生成可执行文件的过程不仅仅是编译的过程,而是要经历四个相互关联的步骤∶预处理(也称
预编译,Preprocessing)、编译(Compilation)、汇编(Assembly)和连接(Linking)。 
命令gcc首先
调用cpp进行预处理,在预处理过程中,对源代码文件中的文件包含(include)、预编译语句(如宏定义define等)进行分析。接着调用cc1进
行编译,这个阶段根据输入文件生成以.o为后缀的目标文件。汇编过程是针对汇编语言的步骤,调用as进行工作,一般来讲,.S为后缀的汇编语言源代码文件
和汇编、.s为后缀的汇编语言文件经过预编译和汇编之后都生成以.o为后缀的目标文件。当所有的目标文件都生成之后,gcc就调用ld来完成最后的关键性
工作,这个阶段就是连接。在连接阶段,所有的目标文件被安排在可执行程序中的恰当的位置,同时,该程序所调用到的库函数也从各自所在的档案库中连到合适的
地方。

四.问答

1)求教 C语言的数学库函数与标准库函数 有什么不同?

c语言的标准变化了好几次,现在说的标准C语言指的是99年制定的C99标准。其中定义的函数库就是C语言标准函数库。具体有哪些你可以查询《C语言参考手册(C:A
Reference Manual,Fifth Edition》。

但事实上,每个C/C++编译器都带有自身的函数库,一般都兼容C标准函数库,但也有个别的有些细节上的出入。所以,一般你只要考察你所用编译器的函数库就行了(通常都可以从帮助中得到)。

数学函数是标准库函数的一种。

标准库函数优先使用了,可实现平台无关。应用程序接口API函数是平台相关的。

编译器函数是指MFC/VCL之类的类库函数吧,打包成程序后必须带上函数库。

标准库函数是由一个语言的标准来决定的,API函数是由操作系统提供的。标准库函数大多数时候也要通过调用api函数来达到目的。但是自己用的时候,我觉得还是能调用标准库函数实现这样就最好。这样移植性好得多。
标准库函数自然是优先了,标准库可实现与平台无关。
如果用到系统编程的话,使用API,但是一般的IDE都会对API有封装,比如mfc,所以使用mfc更方便些。当然有些时候直接用API解决某些问题也是很方便的。

编译器函数?如果指mfc之类的,就是上面我说的,

否则如果你用的C++的话,一般的编译器都有实现标准库.直接使用标准库就行.
还有一些其他的流行的库也可以。C++的准标准库boost,可以参考www.boost.org。
linux下的qt,是个GUI库,类似windows下的mfc,但是是用标准c++实现的。

标准库函数:是一种程序规范,提供一套标准的函数,可能有好几种规范,如c99,标准c
编译器函数:是用来告诉编译器怎么编译你的程序的,如#define,#typedef
API函数:操作系统提供,的函数用来支持这个平台上的操作的,如MFC
标准库是一种规范和标准可以非常方便的跨平台。编译器函数一般是编译器的类库提供的,API一般由操作系统来提供。后两者都有一定的局限性

在Linux系统中,系统调用是作为一种异常类型实现的。它将执行相应的机器代码指令来产生异常信号。产生中断或异常的重要效果是系统自动将用户态切换为核心态来对它进行处理。这就是说,执行系统调用异常指令时,自动地将系统切换为核心态,并安排异常处理程序的执行。

图 1. 清单 1 代码的结构图

图片 1

...

 

Linux用来实现系统调用异常的实际指令是:

带来的问题

  1. SomeFunction()
    函数代码冗余,格式混乱。本例仅涉及三个预编译选项,但实际情况中由于
    Linux
    版本众多并且可能涉及操作系统位数的问题,增加对新系统的支持会导致预编译选项不断增多,造成
    SomeFunction() 函数结构十分混乱。
  2. 新增其他平台相关接口(例如:增加
    SpecialCase3ForRHEL(),SpecialCase3ForSUSE(),SpecialCase3ForUBUNTU),会成倍增加代码中预编译宏的数量。
  3. 破坏了接口平台无关性的原则。SpecialCaseForRHEL(),SpecialCaseForSUSE(),SpecialCaseForUBUNTU()
    只是同一功能各个平台的不同实现,属于封装内容,不应该分开暴露给调用者。

可见,简单利用预编译宏来解决平台相关代码产生的问题不是一个好的方法,并没有解决本文开始提出的两个问题。后文将通过三个方案依次解决这些问题。

2、binutils提供了一系列用来创建、管理和维护二进制目标文件的工具程序,如汇编(as)、连接(ld)、静态库归档(ar)、反汇编
(objdump)、elf结构分析工具(readelf)、无效调试信息和符号的工具(strip)等。通常,binutils与gcc是紧密相集成
的,没有binutils的话,gcc是不能正常工作的。

Int ?$0x80

解决方案 1:根据接口平台无关性原则进行优化

实质上,SpecialCaseForRHEL(),SpecialCaseForSUSE(),SpecialCaseForUBUNTU()
只是同一功能在不同平台上的实现,SpecialCase2ForRHEL(),SpecialCase2ForSUSE(),SpecialCase2ForUBUNTU()
亦如此。对于调用者,应该遵循接口平台无关性的原则,使用统一的接口进行调用,这样才能简化代码,使代码易于维护。

3、glibc是gnu发布的libc库,也即c运行库。glibc是linux系统中最底层的api(应用程序开发接口),几乎其它任何的运行库
都会倚赖于glibc。glibc除了封装linux操作系统所提供的系统服务外,它本身也提供了许多其它一些必要功能服务的实现,主要的如下:
 (1)string,字符串处理
 (2)signal,信号处理
 (3)dlfcn,管理共享库的动态加载
 (4)direct,文件目录操作
 (5)elf,共享库的动态加载器,也即interpreter
 (6)iconv,不同字符集的编码转换
 (7)inet,socket接口的实现
 (8)intl,国际化,也即gettext的实现
 (9)io
 (10)linuxthreads
 (11)locale,本地化
 (12)login,虚拟终端设备的管理,及系统的安全访问
 (13)malloc,动态内存的分配与管理
 (14)nis
 (15)stdlib,其它基本功能

这一指令使用中断/异常向量号128即16进制的80)将控制权转移给内核。为达到在使用系统调用时不必用机器指令编程,在标准的C语言库中为每一系统调用提供了一段短的子程序,完成机器代码的编程工作。事实上,机器代码段非常简短。它所要做的工作只是将送给系统调用的参数加载到CPU寄存器中,接着执行int
?$0x80指令。然后运行系统调用,系统调用的返回值将送入CPU的一个寄存器中,标准的库子程序取得这一返回值,并将它送回用户程序。

转载本站文章请注明出处:vns威尼斯城官网登入 http://www.tiec-ccpittj.com/?p=4710

上一篇:

下一篇:

相关文章