Electronic Joint Business

Solution for E-Business

gcc

操作系统开发初体验(2)C++ 支持代码和控制台

在开始阅读第二篇文章前,我建议你翻阅上一篇文章,不然,接下来的内容可能就用处不大了。

现在我们需要一些支持代码,G++ 会用到其中的一些,不过我们增加了更多的代码以避免奇怪的错误。这里我们要添加的代码有:

  • 对构造器的调用
  • 提供 new 和 delete 的实现
  • 在 GCC 无法调用虚拟方法的时候提供一个方法以供调用。

除了上述三个,还有就是:据说有些操作系统会在内核代码结束的地方调用析构函数,但我没看过这样的案例,大多数多任务操作系统都会在 main 函数的末尾进入无限循环,这样定时器 IRQ 就可以来接管任务。(要晚很多)

首先,我们需要调用构造函数。链接脚本可以指出这些构造函数的指针起始和结束的地址。要调用它们,我们要做只是遍历它们。虽然链接器有可能会弄错顺序,不过概率很低。至少在本系列文章中,我们还是可以信任 GCC 的。

要调用构造器,我们可以使用下面的代码:

>>> 阅读全文

 

, ,

操作系统开发初体验 (1)

文章评价:
如果你正在读这篇文章,没准你想知道更多有关如何创建自己的 OS 方面的内容。不过你首先要知道,我这短短几篇文章是没办法涵盖 OS 开发的各个方面的。我会介绍一些 OS 的基础知识,但还需要靠你自己去研读相关的技术文档,这样你才能明白操作系统为何如此工作。确定还想读下去吗?好,让我们开始吧。

理解 OS 开发很关键的一点是:一切将从零开始,你直接和硬件打交道。你会遇到硬件故障,也不能指望系统每次都如期工作。你没有标准库可供使用,更没有 .NET 框架或者Java 虚拟机。一切只能靠你自己。

开始之前,你要了解系统控制权是如何转交到你手上的。很奇怪的是,没有什么标准规定电脑开机之后应置于什么状态,唯一能确定的是,开机之后一定会去执行引导扇区,它位于可启动的存储介质的起始部分,会被加载到内存地址 0x7C00 处,其长度为 512字节,并以 0xAA55 作为文件末尾签名。引导扇区通常用汇编语言编写。为了节约时间,我们将使用现成的 GRUB (Grand Unified Bootloader)。这可为后续工作提供可靠的基础,并将电脑置于同一个标准状态。

Grub 会将电脑置于以下状态:

  • 保护模式
  • A20 地址线启用
  • 寄存器 EBX 存有指向多重启动信息结构(Multiboot information structure)的指针
  • 寄存器 EAX 存有固定值 0x2BADB002
  • 分页机制关闭
  • 栈位于内存的某处
  • 中断机制关闭

下面我会逐一介绍这些状态。保护模式允许内核开发人员按虚拟地址空间访问最大为 4GB 的内存,并引入了保护环“Ring”的概念。以下几小节会介绍更多的内容。 A20 门电路是键盘控制器附近的一条“古老”的地址线。它最初设计是为了与 8086 保持兼容,在“关闭”的时候,屏蔽了对内存开始部分 1M 以上地址的访问。

>>> 阅读全文

 

, , , , , , , ,

编写 GCC 前端

文章评价:
一、简介
这篇文章演示了如何为 GCC 创建新的前端的基本步骤,它可以帮你在 GCC 编译工具链的基础上创建自己的编译器。另外本文还囊括了一些基础工具的使用方法,如 Bison 和 Flex。阅读本文需要 C 语言的基础知识。你还可以参考介绍编译器基本原理的文章,这可以让你更好地理解相关的内容。1

本文会演示如何在 GCC 编译工具链中添加一种新的迷你语言。虽然 GNU Internal 这部手册已经详细介绍了 GCC 的内部机理,但对新手来说,其内容实在多得可怕。通过本文中的例子,新手也可以开始着手摆弄复杂的 GCC 基础代码。

二、编译器基础知识
编译器本质上就是个翻译器,它读入程序的源代码并将其转换成目标语言,目标语言通常是真实(或虚拟)处理器的汇编代码。设计一个真正的编译器是项复杂的工作,需要有计算机科学和数学的正规课程的背景。

整个编译过程可分为一定量的子任务,也被称为阶段(phase)。通常会涉及以下阶段:

  • 词法分析 – Lexical analysis
  • 语法分析 – Syntax analysis
  • 生成中间代码 – Intermediate code generation
  • 代码优化 – Code Optimization
  • 代码生成 – Code generation

上述所有阶段都会用到符号表错误处理器。我们先来看一下这些阶段:

>>> 阅读全文

 

, , ,

LLVM 简介

本文将讨论让 LLVM 成型的一些设计策略。LLVM 是个一揽子项目(a umbrella project),用于支持和开发一系列紧密相关的底层工具组件(例如汇编器、编译器、调试器等等),从设计之初,LLVM 就考虑兼容现在广泛使用的 Unix 系统工具。 起初 “LLVM” 这个名字是个缩写,现在则是这个一揽子项目的品牌。虽然 LLVM 提供了独特的功能,并因其强大的工具而闻名(例如 Clang 编译器, 一个比 GCC 更强大的 C/C++/Objective-C 编译器),但真正让 LLVM 有别于其他编译器的地方是在其内部架构。

从诞生之初(2000年12月),LLVM 就被设计成一组具有定义良好接口的、可重用的库。当时,开源的编程语言的实现通常按专用工具来设计的,一般只能不可分割地运行(monolithic executables)。比方说,很难从类似 GCC 这样的静态编译器中将解析器分离出来用在静态分析或者重构。而脚本语言大多都提供了将运行时或解释器嵌入大型应用的方法,该运行时也是一个完整的、不可分割的可以被嵌入或者单独使用的代码块。不同语言实现项目之间根本无法重用部件,代码也很难共享。

除了编译器本身的组成外,围绕流行语言的社区往往呈现出很强的两极化倾向:有些实现提供了传统的静态编译器(如 GCC, Free Pascal 和 FreeBasic), 而有的实现则以解释器或JIT(Just-In-Time)编译器的形式提供了运行时编译器。但是很少有实现同时提供两者,即使有的话,两者也极少共享代码。

过去十多年中,LLVM 慢慢改变了这种困境。现在,LLVM 被作为一个通用的基础框架实现了很多静态编译或运行时编译的语言(例如,GCC 支持的语言族、Java、.NET、Python、Ruby、Scheme、Haskell、D语言以及许多的知名度稍低的语言)。它也取代了很多专用编译器,像苹果公司 OpenGL 栈中的运行时特化引擎(runtime specialization engine)以及 Adobe 公司 After Effects 产品中的图形处理库。最后,LLVM 也用来创建各种新产品,最出名的应该算是 OpenCL GPU 编程语言和运行时了。

传统编译器设计简介
传统的静态编译器设计(如大多数 C 编译器)中采用的最普遍的方案就是三阶段编译,该设计主要由三个组件组成,分别是前端、优化器和后端(如图1-1所示)。前端负责解析源代码,检查错误,并构建语言相关的抽象语法树(AST)。AST 可根据情况转换为便于优化的新表示方式, 优化器和后端针对这种新表示而运行。

>>> 阅读全文

 

, , , , , ,

EFI 编程简介

背景
可扩展固件接口( EFI—Extensible Firmware Interface)最早可追溯到 1998年的 Intel Boot Initiative(IBI) 项目。现在 EFI 规范主要由一些公司财团来开发和维护(包括 Intel 和 Microsoft),定义了为系统固件导出的一系列 API 和数据结构,可以被下列客户端所使用

  • 操作系统加载器(boot loader)
  • 操作系统
  • EFI 设备驱动
  • EFI 诊断和系统工具
  • EFI 命令解释器 (shells)

有关 EFI 及其起源的细节,你可以跟随开放固件(Open Firmware)的讨论,在 More Power to Firmware 一节中找到。此外,在 《Mac OS 内部》一书中有一整章专门研讨开放固件和 EFI。

EFI 被引入之后并没有马上得到普及。部分原因要归咎于安腾(Itanium)CPU 乏善可陈。此外,EFI 是一种颠覆性的解决方案,因此问世之后短期内缺乏吸引力。直到苹果为其基于 Intel 的 Macintosh 电脑采用了 EFI ,对 EFI 来说如同一剂强心剂。尤其是,尝试在 Intel CPU 的苹果机上运行非苹果操作系统(主要是 Windows)的狂热,使得人们对 EFI 产生了相当大的兴趣,对应的,是对 EFI 编程的兴趣。

多年来,Intel 已经发布了许多 EFI 相关的软件,包括源代码。Intel 平台的 EFI 创新框架(Intel® Platform Innovation Framework,简称框架)是 Intel 下一代固件架构。这段代码的核心块基于开源许可证,可以在 TianoCore.org 找到。 “Tiano” 是开发代号,而“Framework”是市场名称。 该框架是英特尔的 EFI 规范的实现。

标准 EFI 开发环境
要开始进行 UEFI 开发,你需要用到两个开发包: EFI 开发工具 (EDK ) 和 EFI 工具集 (EFI Toolkit) 。

>>> 阅读全文

 

, , ,

阅读 Grub 源代码 (一) X86 汇编及 GAS 语法

文章评价:

简介
本文的例子采用 AT&T 汇编语法编写,并用 GNU AS 编译的。该语法的主要优点是它与 GCC 内嵌汇编保持着语法兼容性。不过,这并不是表示 X86 操作符唯一语法。例如,NASM、MASM 就使用了完全不同的语法来表示助记符,操作数和寻址模式及其他高级汇编特性。 AT&T 的语法是类 Unix 系统上的标准,但其他使用 Intel 语法的汇编器,比如 GAS 本身,两种语法都可以接受。

GAS 是 GNU 项目的一部分,它具有可以自由使用、适用于多种操作系统、和 GNC 编译器 (gcc) 和 GNU 链接器 (ld) 等编程工具接口良好等等特点。

如果使用安装了 Linux 操作系统的电脑,通常默认就安装了 GAS。如果使用的是 Windows 操作系统,则可以安装 Cygwin 或者 MinGW 来获得 GAS 和其他编程工具。

GAS 指令一般都采用“ 助记符 源,目的地 ”的格式。例如,下面的 mov 指令:

>>> 阅读全文

 

, , , , , ,

C++11 一览 (二)

简介
在本系列文章中的第一部分,我们介绍了 C++11 的一些特性。在本篇文章,我们会继续介绍其它特性。

Strongly typed enums:

As quoted by Stroustrup “C enumerations constitute a curiously half-baked concept” and very few modifications are done to rectify there shortfalls resulting in manifestation of silent behavioral changes. Let’s check an example to support this,

namespace DefenceNetwork
{
   namespace WeatherMonitor
   {
     enum CycloneWarningLevels
     {
        GREY, // Medium winds
        RED, // High speed winds
        YELLOW, // Very High speed winds
        GREEN, // Severe Cyclone
        BLUE // Very Severe Cyclone
     };
   }
   namespace ThreatMonitor
   {
     enum AlertConditions
     {
        BLUE, // Lowest readiness
        GREEN, // Increased intelligence watch
        YELLOW, // Increase in force readiness
        RED, // Next step to nuclear war
        GREY // Nuclear war is imminent
     };
   }
 }
using namespace DefenceNetwork;
void SetDEFCONLevel(int value)
{
   // ….before setting level …lets check the weather once
   using namespace WeatherMonitor;
   // ……
   // …. Ok all is fine! Lets go ahead with setting DEFCON level
   if(value >= RED)
   {
      cout<<"Nuclear war is imminent…All Missiles GO…GO…GO"<<endl;
   }
   // Here, the confusion is AlertConditions::GREEN and CycloneWarningLevels::RED are same
   // as we messed up by putting ‘using namespace WeatherMonitor’.
 }
void main()
{
   using namespace ThreatMonitor;
   // Set level to Increase intelligence watch
   SetDEFCONLevel( AlertConditions::GREEN );
   
  // Oh no… what have I done …How the war is started
  // Hope in reality they have coded better than this…but hey can we take a chance !!!
}

The problem with the enums so far is that

1. They can be silently converted to int.
In the above example SetDEFCONLevel( ) method need int and when we pass an
enumerator it happily accepted.

>>> 阅读全文

 

,

GCC 4.6.0 编译优化选项

应该谨记在心的是,:“使用编译器优化得到的小幅度性能提升,与它带来的风险相比微不足道” — 源自 LFS-Book.

追求极致的优化是一件既耗时又麻烦的事情,你会陷入无止尽的测试、测试、再测试…所以优化应当适可而止为好,将精力留出来用在程序算法的优化上会有更有意义。此本文的主旨是针对 GCC 4.6.0 的优化选项给出一个完整和清晰的脉络,仅仅作为参考。

GCC 编译的基本过程
在 Unix 环境下(包括 Unix、Linux、MSYS 和 Cygwin 等等),软件源代码大多提供了 configure 脚本,该脚本一般都会有许多配置选项,其中有不少选项是与性能息息相关的。对于每个特定的软件包,在编译前应该使用 configure –help 查看所有选项,并精心选择有关选项。运行 configure 脚本后会生成有关项目的 Makefile。将源代码编译为二进制文件是在 Makefile 文件的指导下,由 make 程序调用一条条编译命令完成的。而将源代码编译为二进制文件又需要经过以下四个步骤:预处理(cpp) → 编译(gcc或g++) → 汇编(as) → 连接(ld) ;括号中表示每个阶段所使用的程序,它们分别属于 GCC 和 Binutils 软件包。

尽管将源代码编译为二进制文件的四个步骤由不同的程序( cpp,gcc/g++,as,ld)完成,但是事实上 cpp, as, ld 都是由 gcc/g++ 进行间接调用的。换句话说,控制了 gcc/g++ 就等于控制了所有四个步骤。

显然的,优化应当从编译工具自身的选择以及控制编译工具的行为入手。本文以 Binutils-2.21.1 和 GCC-4.6.1为例进行说明。

>>> 阅读全文

 

,

Gnu Binutils 命令小结

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

而binutils提供了一系列用来创建、管理和维护二进制目标文件的工具程序,binutils与gcc是紧密相集成的,没有binutils的话,gcc是不能正常工作的。 Binutils提供了一下的工具,主要有:
addr2line 把程序地址转换为文件名和行号。在命令行中给它一个地址和一个可执行文件名,它就会使用这个可执行文件的调试信息指出在给出的地址上是哪个文件以及行号。

ar建立、修改、提取归档文件。归档文件是包含多个文件内容的一个大文件,其结构保证了可以恢复原始文件内容。

as主要用来编译GNU C编译器gcc输出的汇编文件,产生的目标文件由连接器ld连接。

c++filt 连接器使用它来过滤 C++ 和 Java 符号,防止重载函数冲突。

>>> 阅读全文

 

, ,

从MinGW交叉编译出MinGW64

MinGW一直没有推出64位的版本,因此在SF.NET上又有一个新的项目叫 MinGW64,虽然大家支持的都是 GCC,但是两个项目是有一些区别的,除了前面提及的32位版和64位版的问题外,主要是二者的运行时不尽相同,MinGW64 重写了自己的 MinGW 运行时,称为 MinGW-W64,和 MinGW 相比,其运行时支持的 Windows API 更多一些。

但是 MinGW64 上发布的二进制包中,缺少一些很有意思的语言工具,比如 ada。而编译ada要求GCC中一定要有 gnat(即 ada 编译器)存在,而目前只有 MinGW 才提供了 ADA 编译器,所以我们必须回到起点,从32位的 MinGW 逐步交叉编译 MinGW64。

这个过程会用到的工具有:MinGW 和 MSYS 的二进制包和以下源代码包,包括:binutils (2.22), GCC (4.7.0),MinGW-W64运行时(2.0.2), gmp (5.0.1), mpfr (3.1.0), MPC (0.9)。 要求 MinGW 中 GCC 工具的版本,最好要编译的源代码一直,避免出现低版本的 GNAT 无法识别高版本的 ADA 语法而报错。

一切就绪之后,可以开始解压源代码包,并将 gmp,mpfr 和 mpc 拷贝到 gcc-4.7.0 的目录中,并去掉名字中的版本号。 我们下来首先会构造出交叉编译的工具来。

创建32位交叉编译环境
1. 创建临时工作目录
我们的32位交叉编译环境将要被发布到 C:\\tools 目录下,打开DOS窗口,创建一个文件夹 tools。之后在 MSYS 安装目录下找到 etc 文件夹里的 fstab 文件,添加一行:

>>> 阅读全文

 

, , , ,

Libffi的使用及其范例

文章评分:

LibFF I与调用约定

“FFI” 的全名是 Foreign Function Interface,通常指的是允许以一种语言编写的代码调用另一种语言的代码。而 “Libffi” 库只提供了最底层的、与架构相关的、完整的”FFI”,因此在它之上必须有一层来负责管理两种语言之间参数的格式转换。

高级语言编译器产生代码时都会依据一系列的规则,这些规则十分必要,特别是对独立编译来说。其中之一是“调用约定” (Calling Convention),它包含了编译器关于函数入口处的函数参数、函数返回值的一系列假设。它有时也被称作“ABI”(Application Binary Interface)。调用约定(Calling Conventions)定义了程序中调用函数的方式,它决定了在函数调用的时候数据(比如说参数)在堆栈中的组织方式。

通常来说函数调用要用到的两条基本的指令:”CALL”指令和”RET”指令。”CALL”指令将当前的指令指针(这个指针指向紧接在CALL指令后面的那条指令)压入堆栈,然后执行一条无条件转移指令转移到新的代码地址。”RET”是与”CALL”指令配合使用的指令,在绝大多数函数中它是最后一条指令。”RET”指令弹出返回地址(就是早些时候”CALL”指令压入堆栈的地址)并将其加载到”EIP”寄存器中,然后从这个地址开始继续执行。

>>> 阅读全文

 

, , , , ,

使用MinGW64编译GCC 4.6和4.7

gcc 4.6.0简介
GNU在3月25日正式放出了GCC 4.6.0的Tarball,不过在GNU的站点上也可以下载到GCC 4.7的Snapshot,你可以从GCC SnapShot站点, 下载到最新的版本。GCC 4.7的编译过程和GCC 4.6一致。

GCC 4.6支持以下这样一些新特性。除了支持一些新的鸡肋语言,比如Go,主要更新亮点有:

  • 大大改进了过程间分析和链接时优化。
  • 增加了128位浮点数支持,这个在科学计算领域应该很有用,X86和安腾系的芯片首先得益。
  • 一些详细的细节包括:

  • 增加更强力的编译优化选项-ofast,这个选项除了打开-O3的所有优化外,还开启了所有能开启的优化,以获得更好的性能,如-ffast-mast优化
  • 改进链接时优化(LTO): ◦全局可扩展优化器已经足够稳定。打开LTO后会默认开启此优化。该优化器可以自动分割成几个并行的编译过程,可以用选项-flto=n指定并行编译进程个数。
  • 增加-fstack-usage选项。编译器可以输出程序的栈使用信息
  • 你还可以参考GCC 4.6的release notes,该页面上有更详细的信息。

    准备工作
    为了开始编译GCC,你需要安装一份MinGW64的拷贝和MSYS。我们首先安装MinGW64,将其压缩包解开即可,在本文中MinGW64为C:\MinGW64。

    >>> 阅读全文

     

    , ,

    在 Eclipse 中集成 MinGW64 开发环境

    文章评价:

    本文主要讨论如何集成 Eclipse 和 MinGW64 来进行 C/C++ 应用程序的开发。由于不同版本 Eclipse CDT 的稳定性表现不一,因此需要你合理选择 Eclipse 的版本,现在已知 Helios (Eclipse 3.6) 的 CDT 在开发中会频繁崩溃,不建议使用。本文选择的是 Eclipse 4.3.1 来讲解,其他版本方法基本相同。

    GCC编译器
    首先要做的是下载一个合适的 MinGW64 编译器。要开发并运行 64 位的 Windows 应用,你可以选择 32 位的 GNU C/C++ 编译器并通过交叉编译来产生 64 位应用, 也可以选择纯 64 位的编译器。在 MinGW64 的官方站点上提供了许多不同的版本,你可以在 EJB.CC 下载到 GCC 4.6.1 for Windows ,或者在SourceForge.net上选择最新的版本。

    同时还需用到 MSYS(在 MinGW 的站点上提供),MSYS 提供一些常见的 Linux 命令工具用于 Windows GCC 的编程开发,目前只有 32 位版本,不过不会对代码生成有任何影响。

    接下里要做的就是为 Eclipse 安装最新的 CDT,目前 Galieo 只支持 CDT 6.0,Helios 则支持 CDT 7.0,而 Indigo 则支持 CDT 8.0,以此类推。如果你下载的 Eclipse 并未集成 CDT,你可以从 “Help” 菜单下选择 “install new software”,之后在 “avaiable software” 中的 “programming languages” 中选择各个 C++ 子项并安装。

    >>> 阅读全文

     

    , , , , ,

    详解GNU Make (二) 高级用法

    在上一篇文章中,我们了解了 makefile 的基本结构和一些常见用法,在这篇文章中我们会更深入地了解一些“高级用法”,这些用法在实际的例子中相当常见,可以极大地提高 makefile 的编写效率。

    Makefile 里有什么?
    Makefile 里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。

    • 显式规则。显式规则说明了如何生成一个或多的的目标文件。这是由 Makefile 的编写者显示地指出:要生成的文件、文件的依赖文件和生成命令。
    • 隐晦规则。由于 make 有自动推导的功能,所以隐晦规则可以让编写者比较简略地编写 Makefile,这是 make 所支持的。
    • 变量定义。在 Makefile 中我们可以定义一系列的变量,变量一般都是字符串,有点 C 语言中的宏,当 Makefile 被执行时,其中的变量都会被扩展到相应的引用位置上。
    • 文件指示。其包括了三个部分:一是在 Makefile 中引用另一个 Makefile,类似 C 语言中的 #include;二是指根据某些情况指定 Makefile 中的有效部分,类似 C 语言中的条件编译 #if ;三是定义多行的命令。有关这一部分的内容,我会在后续的部分中讲述。
    • 注释。Makefile 中只有行注释,和 UNIX 的 Shell 脚本一样,其注释是用“#”字符, 类似 C/C++ 中的“//”。如果要在 Makefile 中使用“#”字符,可以用反斜杆进行转义:“\#”。

    最后要注意的是,在 Makefile 中的命令,必须要以 Tab 键开始。

    Makefile 的文件名

    默认的情况下,make 命令会在当前目录下按顺序找寻文件名为 “GNUmakefile”、“makefile”、“Makefile” 的文件,找到了就解释这个文件。在这三个文件名中,最好使用 “Makefile” 这个文件名,因为,这个文件名第一个字符为大写比较显目。最好不要用 “GNUmakefile”,这个文件是 GNU 的 make 识别的。有另外一些 make 只对全小写的 “makefile” 文件名敏感,但是基本上大多数的 make 都支持 “makefile” 和 “Makefile” 这两种默认文件名。

    >>> 阅读全文

     

    , , , , , ,

    详解GNU Make (一) 基本规则

    文章评价:
    一个较大的项目会有许许多多源文件,一般我们会按类型、功能、模块等类别分别放在若干个目录中,makefile 提供了一系列的规则用于指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile 就像一个 Shell 脚本一样,其中也可以执行操作系统的命令。

    因此,makefile 关系着整个项目的编译规则,带来的好处就是“自动化编译”。一旦写好 makefile,你只需要运行 make 命令,整个工程完全自动编译,极大地提高了软件开发的效率。

    负责解释 makefile 中指令的工具是 make。一般来说,大多数的 C/C++ 编译器都提供这个工具,最常见的有:Visual C++ 的 nmake,GNU 的 make。但是不同产商的 make 各不兼容,makefile 语法也略有不同。这里我们以应用最为广泛的 GNU make 来做讲解。

    Makefile 介绍
    我们先引用 GNU 的 make 使用手册中的一个例子,假设我们的工程有 8 个 C 文件和 3 个头文件,要如何编写 Makefile 来实现自动编译和链接这几个文件。我们的要求是:
    1)如果这个工程没有编译过,那么我们的所有 C 文件都要编译并被链接。
    2)如果这个工程的某几个 C 文件被修改,那么我们只编译被修改的 C 文件,并链接目标程序。
    3)如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的 C 文件,并链接目标程序。

    所有这些要求,只需要一个 makefile 就可以完成了。make 命令会自动智能地根据当前的文件修改的情况来确定哪些文件需要重编译,从而自己编译所需要的文件和链接目标程序。

    >>> 阅读全文

     

    , , , , ,

    Previous Posts