程序环境和预处理

前言:内容包括:程序的翻译环境和执行环境,详解编译,链接,预处理详解

1 程序的翻译环境和执行环境

目录

1 程序的翻译环境和执行环境

 2 详解编译、链接

      翻译环境:

预处理阶段: 

编译阶段:

 汇编阶段:

​编辑 

链接阶段: 

 运行环境

3 预处理详解

预定义符号

 #define

#define定义标识符

#define定义宏

#define的替换规则

宏和函数的对比

宏的缺点

命名约定:

#undef

 条件编译

文件包含

避免头文件的重复引入


ANSI C 的任何一种实现中,存在两个不同的环境:
翻译环境:在这个环境中源代码被转换成可执行的机器指令(二进制指令)
执行环境:实际执行代码
程序环境和预处理_第1张图片

 2 详解编译、链接

      翻译环境:

程序环境和预处理_第2张图片

程序环境和预处理_第3张图片 

 

下面我用gcc来演示:

预处理阶段: 

 程序环境和预处理_第4张图片

编译阶段:

程序环境和预处理_第5张图片

 

 程序环境和预处理_第6张图片

 汇编阶段:

程序环境和预处理_第7张图片

程序环境和预处理_第8张图片 

链接阶段: 

 程序环境和预处理_第9张图片

 程序环境和预处理_第10张图片

 运行环境

程序执行的过程:
1. 程序必须载入内存中
   在有操作系统的环境中:一般由操作系统完成
   在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成
2. 程序的执行开始,接着便调用 main 函数
3. 开始执行程序代码
    这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址
    程序同时也可以使用静态(static )内存,存储于静态内存中的变量在程序的整个执行过程
    一直保留他们的值
4. 终止程序
    正常终止 main 函数or意外终止

3 预处理详解

预定义符号

__FILE__      //进行编译的源文件
__LINE__     //文件当前的行号
__DATE__    //文件被编译的日期
__TIME__    //文件被编译的时间
__STDC__    //如果编译器遵循ANSI C,其值为1,否则未定义

这些预定义符号都是语言内置的,可以直接使用

#include

extern int Add(int x, int y);//声明外部符号

int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	int c = Add(a, b);
	printf("%d----%s,%s,%s,line = %d\n", c,__FILE__,__DATE__,__TIME__,__LINE__);
	return 0;
}

 

 #define

#define定义标识符

语法:
语法:
 #define name stuff

实例:

#define MAX 100

MAX的内容就是1000

#include

#define MAX 100

int main()
{
	printf("%d", MAX);
	return 0;
}

 

注意:#define定义标识符时不要再最后加上分号

#define定义宏

#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏( macro )或定义
宏( define macro
语法:
#define name( parament-list ) stuff

实例:

#define MAX(x,y) ((x)>(y)?(x):(y))
#include

#define MAX(x,y) ((x)>(y)?(x):(y))

int main()
{
	int a = 3;
	int b = 5;
	int ret = MAX(3, 5);
    printf("较大值为:%d", ret);
	return 0;
}

 注意:

参数列表的左括号必须与 name紧邻
如果两者之间有任何空白存在,参数列表就会被解释为 stuff 的一部分
#fdefine定义的标识符或者宏,都是直接替换到代码中的,若是宏的参数部分是个表达式,则直接将完整的表达式替换过去,不会先计算出一个结果再替换
在#define定义宏时,注意多使用括号

#define的替换规则

1. 在调用宏时,首先对参数进行检查,看看是否包含任何由 #define 定义的符号
    如果是,它们首先被替换。
2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换
3. 最后,再次对结果文件进行扫描,看看它是否包含任何由 #define 定义的符号
    如果是,就重复上述处理过程
注意:
1. 宏参数和 #define 定义中可以出现其他 #define 定义的符号
    但是对于宏,不能出现递归
2. 当预处理器搜索 #define 定义的符号的时候,字符串常量的内容并不被搜索

宏和函数的对比

功能简单,采用宏来实现

功能复杂,采用函数来实现

比如执行简单的运算,我们使用宏:

求出两个数中的较大值:

#define MAX(a, b) ((a)>(b)?(a):(b))

不使用函数的原因:

#include

int Max(int x, int y)
{
	return x > y ? x : y;
}

int main()
{
	int a = 3;
	int b = 5;
	int ret = Max(3, 5);
	printf("较大值为:%d", ret);
	return 0;
}
a.函数调用的时间花费:
1 函数调用前的准备(传参,函数栈帧空间的维护等)
2 主要运算
3 函数返回,返回值的处理,函数栈帧的销毁
宏的时间花费:
主要运算(直接替换)
   所以宏比函数在程序的规模和速度方面更胜一筹
b.函数的参数必须声明为特定的类型。
所以函数只能在类型合适的表达式上使用
而宏可以适用于整形、长整型、浮点型等可以 只要是能够用> 来比较的类型
宏是类型无关的

宏的缺点

1. 每次使用宏的时候,一份宏定义的代码将插入到程序中
    除非宏比较短,否则可能大幅度增加程序的长度
2. 宏是没法调试的
3. 宏由于类型无关,也就不够严谨
4. 宏可能会带来运算符优先级的问题,导致程容易出现错
   比如:
#include

#define S(x,y) x*y
int main()
{
	int a = 3;
	int b = 5;
	int ret = S(3+1, 5+2);
	//原本想要的计算结果是:4*7 = 28
    //实际上:3+1*5+2 = 10
	printf("%d", ret);
	return 0;
}

但是宏的参数可以出现类型

比如:

#include

#define MALLOC(num,type) (type*)malloc(num*sizeof(type))
int main()
{
	int* p = MALLOC(3, int);
	char* str = MALLOC(10, char);
	return 0;
}

定义一个有malloc函数功能的宏MALLOC,可以申请存放不同类型数据的空间

命名约定:

把宏名全部大写
函数名不要全部大写

#undef

用于移除一个宏定义

#include

#define MAX 100
int main()
{
	printf("%d\n", MAX);//MAX有效
#undef MAX
	printf("%d\n", MAX);//MAX无效
	return 0;
}

 程序环境和预处理_第11张图片

 条件编译

#if 常量表达式
……
#endif
int main()
{
#if 1==2
	{
		printf("1");//不打印
	}
#endif
	return 0;
}

#if 常量表达式
 //...
#elif 常量表达式
 //...
#else
 //...
#endif
int main()
{
#if 1==2
	{
		printf("1");//不打印
	}
#elif 1==8
	{
		printf("2");
	}
#elif 3==4
	{
		printf("3");
	}
#else
	{
	    printf("4");//打印
    }
#endif
	return 0;
}

3 判断是否被定义

#if defined(symbol) //第一种表达
#ifdef symbol       //第二种表达
#if !defined(symbol)//第一种表达
#ifndef symbol      //第二种表达

文件包含

头文件被包含:

本地文件包含
#include "filename"
查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标 准位置查找头文件
如果找不到就提示编译错误
库文件包含
#include 
查找头文件直接去标准路径下去查找,如果找不到就提示编译错误

避免头文件的重复引入

每个头文件的开头写:

#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
  ……
#endif  
#ifndef __TEST_H__
#define __TEST_H__
int Add(int x, int y);
#endif

__TEST_H__是test.h文件的名字,根据具体头文件的名字来定

或者:
#pragma once
#pragma once
int Add(int x, int y);

你可能感兴趣的:(c语言知识,c语言)