目录
(一)翻译环境
(1)组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。
(2)每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
编译 + 链接 运行
text.c——————-------翻译环境—-------——————>text.exe-——运行环境————>
文本文件 二进制文件
(源文件 源程序) (可执行程序)
源文件1(.c) | 目标文件1(.obj) |
源文件2 | 目标文件2 |
源文件3 | 目标文件3 |
链接库 | 链接器(目标文件通过链接器生成可执行程序(.exe)) |
1.1编译
编译又可以分为三个阶段
预编译 | 编译 | 汇编 |
预编译(文本操作)
(1)在linux环境下: 如果输入gcc.-E text.c 进行预编译
(2) #include头文件的包含在预编译系统就进去了
(3)预编译的时候删除注释(使用空格来替换注释)
(4) 预处理阶段也会完成#define
编译:把c代码翻译为汇编代码
语法分析 词法分析 语义分析 符号汇总(函数名,全局变量)
汇编:形成符号表
(1) 链接:
1.合并段表
2.符号表达的合并和重定位
(2)合并段表:生成的目标文件(.o)有固定格式(elf),会生成好几个段,但放的内容不一样,需要将它们合并,exe的文件格式也是(elf)
符号表:
add | 0x000 |
main | 0x200 |
add ox100 |
合并之后
add | ox100 |
main | 0x200 |
(二)运行环境
1.程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
(三)预定义符号
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
#include<stdio.h>
int main()
{
printf("%s",_FILE_);
printf("%d",_LINE_);
}
#include<stdio.h>//编写日志
int main()
{
int i;
int arr[10]={10};
FILE*pf=fopen("log.txt","w");
for(i=0;i<10;i++)
{
arr[i]=i;
fprintf(pf,"file:%s line:%d date:%s i=%d",
_FILE_,_LINE_,_DATE_,_TIME_,i);
}
fclose()
for(i=0;i<10;i++)
{
printf("%d",arr[i]);
}
}
3.1常见预处理指令
#define #include #pragma pack[4] #pragma #if #endif #itdef #line
#define MAX 1000
#define reg register //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;) //用更形象的符号来替换一种实现
#define CASE break;case //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
date:%s\ttime:%s\n" ,\
__FILE__,__LINE__ , \
__DATE__,__TIME__ )
#define do_forever for(;;)
int main()
{
for(;;)
死循环的内容
;
return 0;
}
3.2#define的替换
#define包含了一个规定,允许把参数替换到文本中。这种实现称为定义宏。宏不可以递归。字符串中内容并不会被替换。
#define name( parament-list ) stuff
//参数列表的左括号必须与name紧邻。
//如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分
#define square(x) x*x
int main()
{
int ret=square(5+1);
return 0;
}//红的参数是替换的而不是传参的 5+1*5+1=11
#define square(x) (x)*(x)
int main()
{
int ret=square(5+1);
return 0;
}//(5+1)*(5+1)=36//写宏的时候不要吝啬括号
note:
(1)所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。
(2)在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
3.3##和#的关系
把参数插入字符串。#x会把内容变成”a"
##可以把位于它两边的符号合成一个符号。
#define CAT(X,Y) X##Y
int main()
{
int class84=2020;
printf("%d",CAT(class,84));
//等价于printf("%d",class84);
//##可以把位于它俩边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符
}
printf("woai");==printf("wo""ai");//俩个字符串会自动连在一起。
#define print(x) printf("the value of"#x"is %d\n",x)
int main()
{
int a=10;
int b=20;
print(a);
print(b);
}
//带有副作用的宏
#include<stdio.h>
#define MAX(X,Y) (X)>(Y)?(X):(Y)
int main()
{
int a=10;
int b=11;
int max=MAX(a++,b++);
//int max=(a++)>(b++)?(a++):(b++);
printf("%d%d%d",max,a,b);//12 11 13
}
3.4 宏和函数的对比
函数调用的时候会有函数返回和调用的开销,宏在预处理阶段就完成了替换,没有函数的调用和返回的开销。
宏与类型无关,而函数必须声明类型。
但是,每次使用宏的时候,一份宏的代码插入到程序中,会增加程序的长度。并且宏没办法调试,宏与类型无关则不够严谨。宏会带来运算的优先级问题容易出错。
#define MALLOC(num,type) (type*)malloc(num*sizeof(type))
int main()
{
int *p=MALLOC(10,int);
}
#undef NAME
//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。
//命令行约定
//允许在命令行定义符号,开启自动编译的过程
//例如当我们要根据同一个文件编译出不同版本的时候使用
#include <stdio.h>
int main()
{
int array [ARRAY_SIZE];
int i = 0;
for(i = 0; i< ARRAY_SIZE; i ++)
{
array[i] = i;
}
for(i = 0; i< ARRAY_SIZE; i ++)
{
printf("%d " ,array[i]);
}
printf("\n" );
return 0;
}
(四)条件编译
{
#ifdef debug;//如果debug被定义过下面的句子参与编译 定义在上面用#define
/*#ifdef defined(debug)
printf("%d",a);
#endif*/
}
1.#if 常量表达式
//...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
//..
#endif
2.多个分支的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
4.嵌套指令
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif
#ifndef _test_h
#define _test_h
//头文件的内容
#endif
或者#pragma once
(五)include的介绍
5.1头文件被包含的方式
5.2嵌套文件包含
解决方法:
文件开头写:
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif //__TEST_H
//或者写 #pragma once