c进阶之程序环境和预处理

程序环境和预处理

本篇重点
认识程序环境和预处理

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

在ANSI C的任何一种实现中,存在两个不同的环境。

  • 第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令(二进制指令)。
    在这里插入图片描述
  • 第2种是执行环境,它用于实际执行代码。

2.编译和链接在这里插入图片描述

2.1翻译环境

编译过程:
在这里插入图片描述

2.2程序翻译的几个阶段

在这里插入图片描述

int add(int x, int y)
{
	//add.c
	return x + y;
}
#include<stdio.h>
#define m 100
int val = 9;
//test.c
int main()
{
	int x = 0;
	int y = 0;
	scanf("%d %d", &x, &y);
	int ret=add(x,y);
	printf("%d %d", val, ret);
	return 0;
}

图像理解:
在这里插入图片描述
在这里插入图片描述

在vs2022上不好观察编译的过程,有兴趣的小伙伴可以用gcc编译器观察编译的过程:
一些借本指令:
1.
gcc test.c -E可以将预编译完成后终止编译
gcc test.c -O对编译好的文件进行重命名
预处理完成之后就停下来,预处理之后产生的结果都放在test.i文件中。
2.
编译 选项 gcc -S test.c
编译完成之后就停下来,结果保存在test.s中
3.
汇编 gcc -c test.c
汇编完成之后就停下来,结果保存在test.o中(目标文件)
readelf,elf是一种文件格式(将.o文件分成一个一个段),test.o,test.exe都是以这种格式组织的。

2.3运行环境

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

3.预处理详解

3.1预定义符号

__ FILE__ //进行编译的源文件
__ LINE__ //文件当前的行号
__ DATE__ //文件被编译的日期
__ TIME__ //文件被编译的时间
__ FUNCTION__//打印函数名
__ STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义

#include<stdio.h>
int main()
{
	printf("%s\n", __DATE__);
	printf("%s\n", __TIME__);
	printf("%s\n", __FILE__);
	printf("%d\n", __LINE__);
	printf("%s\n",__FUNCTION__);
	printf("%d\n", __STDC__);
	return 0;
}

这些都是在预处理中被替换。

3.2#define

#define定义标识符

#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定义宏

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

#define name( parament-list ) stuff
//其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中

宏是直接替换的,不会加括号的,要加括号避免不必要的麻烦,自己根据实际情况加括号。
一些宏的常见错误:

#include<stdio.h>
#define mul(x) x*x//改法:(x)*(x)
int main()
{
	int a = 5;
	printf("%d", mul(a + 1));//5+1*5+1=11
	return 0;
}
#include<stdio.h>
#define mul(x) (x)+(x)//改法:((x)+(x))
int main()
{
	int a = 5;
	printf("%d", 10*mul(a + 1));//10*6+6=66
	return 0;
}

3.3#define的替换规则

在程序中扩展#define定义符号和宏时,需要涉及几个步骤。

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先
    被替换。
  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
#include<stdio.h>
#define mul(x) ((x)+(x))
#define a 5
int main()
{
	printf("%d", mul(a + 1));
	return 0;
}

先替换a,即printf(“%d”, mul(5 + 1));,再替换x,即#define mul(5+1) ((5+1)+(5+1)),最后再替换mul(5+1),即printf(“%d”, ((5+1)+(5+1)));
注意:

  1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
  2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

3.4#与##

#include<stdio.h>
int main()
{
	printf("hello"   "world\n");
	return 0;
}

打印出来的结果是什么?

helloworld

vs会将两个字符串合并在一起的。

如何把参数插入到字符串中?

#include<stdio.h>
#define PRINT(x) printf("the value of "#x" is %d\n",x)
int main()
{
	int a = 10;
	PRINT(a);//将“a”插入
	return 0;
}
#include<stdio.h>
#define PRINT(format,x) printf("the value of "#x" is "format"\n",x)
int main()
{
	int a = 10;
	PRINT("%d", a);
	float b = 3.14;
	PRINT("%f", b);
	return 0;
}

加了#相当于替换参数名,不加#号相当于替换参数的值
##的作用:

##可以把位于它两边的符号合成一个符号。
它允许宏定义从分离的文本片段创建标识符。

#include<stdio.h>
#define Cat(x,y) x##y
int main()
{
	Cat("Class", "-w302");
	printf("%s\n", Cat("Class", "-w302"));
	return 0;
}

3.5带副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。

#include<stdio.h>
#define Max(x,y) (x)>(y)?(x):(y)
int main()
{
	int a = 4;
	int b = 6;
	int ret = Max(a++, b++);
	//(a++)>(b++)?(a++):(b++)
	// a=5   b=7         b=8
	printf("%d\n%d %d", a, b, ret);
	return 0;
}

宏和函数的比较:

在这里插入图片描述
函数是做不到实参传的是类型,宏是可以的:

#include<stdio.h>
#define MALLOC(num,type) (type*)malloc(num*sizeof(type))
int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	int* pp = MALLOC(10, int);
	return 0;
}

命名约定:
把宏名全部大写
函数名不要全部大写

3.6#undef

在这里插入图片描述
这条指令用于移除一个宏定义。

3.7命令行定义

许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。
例如:当我们根据同一个源文件要编译出一个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大些,我们需要一个数组能够大些。)

#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;
}
//linux 环境演示
gcc -D ARRAY_SIZE=10 programe.c

3.8条件编译

在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。

调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译

#include<stdio.h>
int main()
{
#ifdef PRINT
	printf("hehe");
#endif
	return 0;
}
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
#include<stdio.h>
#define M 9
int main()
{
#if M==1
	printf("hehe");
#elif M==2
	printf("haha");
#else
	printf("heihei");
#endif//注意这个不要忘记
	return 0;
}
#include<stdio.h>
#define M 1
int main()
{
#ifdef M
	printf("hehe");
#endif
#ifndef N
	printf("haha");
#endif
	return 0;
}

3.9文件包含

  1. 头文件中的 ifndef/define/endif或者#pragma once是干什么用的?
  2. #include <filename.h> 和 #include "filename.h"有什么区别?
    1.为了防止头文件被多次包含(头文件每包含一次就会替换一次,多次包含费时间)。
    2.尖括号包含的头文件是直接去标准库中去查找
    双引号包含的头文件是先去我们写好的项目中寻找头文件,没找到再去标准库中去查找。

总结

以上就是本篇的所有内容了,如果喜欢本篇,不妨点个赞,如有问题,欢迎评论区提问,谢谢大家的观看。

  • 12
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值