程序的环境和预处理

目录

(一)翻译环境

1.1编译                                            

编译:把c代码翻译为汇编代码 

汇编:形成符号表

 (二)运行环境

(三)预定义符号

3.1常见预处理指令

3.2#define的替换

3.3##和#的关系

3.4 宏和函数的对比

(四)条件编译

(五)include的介绍

5.1头文件被包含的方式

5.2嵌套文件包含


ANSI C 的任何一种实现中,存在两个不同的环境。
1 种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。
2 种是执行环境,   它用于实际执行代码。

(一)翻译环境

 (1)组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。

(2)每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。

(3)链接器同时也会引入标准 C 函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人
的程序库,将其需要的函数也链接
                 编译  + 链接                          运行
 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)

       符号表:

        

add0x000
main0x200

       

add  ox100

         合并之后

addox100
main0x200

 (二)运行环境

1.程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。

2. 程序的执行便开始。接着便调用 main 函数。

 

 

3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈( stack ),存储函数的局部变量和返回 地址。程序同时也可以使用静态( static )内存,存储于静态内存中的变量在程序的整个执行过程 一直保留他们的值。
4. 终止程序。正常终止 main 函数;也有可能是意外终止

(三)预定义符号

__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) 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
(4) 最后,再次对结果文件进行扫描,看看它是否包含任何由 #define 定义的符号。如果是,就重复上 述处理过程。
注意:
1. 宏参数和 #define 定义中可以出现其他 #define 定义的变量。但是对于宏,不能出现递归。
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头文件被包含的方式

本地文件包含
查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标 准位置查找头文件。 如果找不到就提示编译错误。
Linux环境的标准头文件的路径: / usr / include 
注意按照自己的安装路径去找。
库文件包含
查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。
对于库文件也可以使用 “” 的形式包含, 但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。

5.2嵌套文件包含

 解决方法:

文件开头写:

#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif   //__TEST_H

//或者写 #pragma once

            

           

             

   

      

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

太一TT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值