015+limou+C语言深入知识——(7)编译环境和运行环境以及预处理指令

这篇具有很好参考价值的文章主要介绍了015+limou+C语言深入知识——(7)编译环境和运行环境以及预处理指令。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

001、ANSI C实现的“翻译环境”和“执行环境”

(1)翻译环境

在这个环境中,源代码被转化为可执行的机器指令(二进制指令)

  • 单文件简易版本
    015+limou+C语言深入知识——(7)编译环境和运行环境以及预处理指令

  • 多文件简易版本
    015+limou+C语言深入知识——(7)编译环境和运行环境以及预处理指令

  • 编译链接详细版本
    015+limou+C语言深入知识——(7)编译环境和运行环境以及预处理指令

    • VS2022集成IDE(windows下)的编译器叫cl.exe,链接器叫link.exe
    • gcc编译器(windows下)的几个有关编译环境的命令
----预处理gcc 文件.c -E -o 文件.i,但直接放在终端了,故需要-o(output)重定向到文件内部
----编译gcc -S 文件名,生成.s文件
----汇编gcc -c 文件名,生成.o文件

----直接gcc 文件名字,直接经过整个翻译环境(预处理、编译、汇编、链接)生成可执行程序a.exe

015+limou+C语言深入知识——(7)编译环境和运行环境以及预处理指令

(1)符号表会把全局变量和具有外部链接的函数等标识符和地址记录下来(局部变量是程序在运行的时候才会创建,而创建符号表还在编译阶段),汇总成表(函数声明部分会给予一个无效的地址,以便后续和函数定义整合在一起)
(2)链接库是属于库函数的
(3)目标文件是二进制文件,这个文件是有格式的,在linux环境下,gcc产生的目标文件和可执行程序的格式都是elf,而这种文件格式将.o文件分为一个一个段,因此在有多个.o文件的情况下,链接器将这些.o文件对应的、相同数据的段合并在一起
(4)再将之前有多个源文件生成的多个符号表合并,重定位指的是:链接的时候整合出一份新的符号表的同时查看标识符和地址是否正确(比如,这个时候如果找不到函数定义,则根据之前给予函数声明的无效地址就会提示编译器“不存在该函数(即链接错误)”),因此,实际上调用函数的时候,就是在链接的过程后才能实现的
015+limou+C语言深入知识——(7)编译环境和运行环境以及预处理指令

(2)运行环境

在这个环境中,主要是实际执行代码的过程

  • 程序必须要载入内存
    • 在有操作系统的环境中:一般这个由操作系统完成
    • 在独立的环境中:程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成
  • 程序开始调用主函数
    • 找到程序入口,即调用main函数
  • 开始执行程序代码
    • 按住程序逻辑顺序开始执行编写的代码
    • 这个时候程序将使用一个运行时堆栈(即“函数栈帧”),存储函数的局部变量和函数的返回地址
    • 程序同时也可以使用静态内存,存储于静态内存中的变量在程序的整个执行过程一直被保留
  • 终止程序
    • 正常终止main函数(也有可能是意外终止)

002、预编译的详细解读

(1)预定义符号

__FILE__    //进行编译的源文件名称
__LINE__    //显示文件当前的行号
__DATE__    //文件被编译的日期
__TIME__    //文件被编译的时间
__STDC__    //如果编译器严格遵循ANSI C,其值为1,否则未定义
__FUNCTION__    //程序预编译时预编译器将用所在的函数名,返回值是字符串
__FUNCDNAME__   //和上面的宏类似,都是显示重命名后的函数
//演示代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
    printf("%s\n", __FILE__);   //进行编译的源文件名称
    printf("%d\n", __LINE__);   //显示文件当前的行号
    printf("%s\n", __DATE__);   //文件被编译的日期
    printf("%s\n", __TIME__);   //文件被编译的时间
    //printf("%d\n", __STDC__); //当编译器严格遵循ANSI C时,则其值为1,否则未定义
    return 0;
}
//实际上,编译器在代码编译的时候,会对函数和变量名重命名
//在C语言中重命名的规则是加上下划线
//在C++中重命名的规则 会更加复杂
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
    printf("%s\n", __FUNCDNAME__);
    printf("%s\n", __FUNCTION__);
    return 0;
}

(2)预编译指令(宏编译指令):#define和#undef

①宏命名约定

一般来讲把宏名全部大写

②宏标识符和定义宏

  • 宏标识符
#include <stdio.h>
#define NUM 10
int main()
{
    printf("%d", NUM);
    return 0;
}
  • 定义宏体
#include <stdio.h>
#define ADD(X, Y) ((X) + (Y))//注意定义宏名和参数列表之间不能有空格,否则就会将()一起作为替换内容
int main()
{
    printf("%d", ADD(2, 3));
    return 0;
}
  • 取消宏定义
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#define NUMBER 10
#define ADD(X,Y) ((X) + (Y))
int main()
{
    printf("%d", NUMBER);
#undef NUMBER
    printf("%d", NUMBER);//这个语句是没办法识别NUMBER这个宏的
    
    printf("%d", ADD(1, 2));
#undef ADD
    printf("%d", ADD(1, 2));//这个语句是没办法识别ADD这个宏的
    
    return 0;
}

③宏替换规则

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

    • 在调用定义宏时,首先对参数进行检查,看看其是否包含任何由#define定义的符号。如果是,它们首先被替换
    • 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换
    • 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程
  • 注意:

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

④定义宏和函数的比较

实际上在后来的C/C++里面引入了关键字内联(inline)具有函数的优点又具有宏的优点,可以把函数像宏一样直接替换在后续的代码中,可以理解为“加强版的宏”,不过这个以后再学…
015+limou+C语言深入知识——(7)编译环境和运行环境以及预处理指令

//举例一个传标识符参数的宏
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#define MALLOC(num, type) ((type*)malloc(num * sizeof(type)))//传递了int过来,这是函数不能做到的
int main()
{
    int* p = MALLOC(10, int);
    if (!NULL)
    {
        return 0;
    }
    p[0] = 2;
    p[1] = 0;
    p[2] = 2;
    p[3] = 3;
    for (int i = 0; i < 4; i++)
    {
        printf("%d ", p[i]);
    }
    free(p);
    return 0;
}

⑤宏处理辅助操作符“\”、“#”、“##”

  • 使用“\”可以进行代码换行操作(只是物理换行而不是逻辑换行)
  • #可以达到保留原本传递字符串的效果,而不是作为替换值,即“把一个宏参数变成对应的字符串”,或者说“把参数名字直接插入”
  • ##可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符,效果就是“两个字符串合成”,而这种字符串合成要更加强大
//示范一
#include <stdio.h>
#define ADD(X, Y) \
((X) + (Y))
int main()
{
    printf("%d", ADD(2, 3));
    return 0;
}
//示范二
#include <stdio.h>
#define PRINTF(num, format)\    //这里也用了换行符
        printf("The value of "#num" is "format, num)
int main()
{
    int a = 100;
    char b = 'c';
    double c = 3.14;
    PRINTF(a, "%d\n");
    PRINTF(b, "%c\n");
    PRINTF(c, "%f\n");
    return 0;
}
//示例三
#include <stdio.h>
#define FUN(X, Y) X##Y
int main()
{
    int XY = 10;
    printf("%d\n", FUN(X, Y));//依旧可以打印出10,X和Y被FUN连接为一串字符串了,并且整体作为变量名
    return 0;
}

⑥使用注意事项

  • 加分号and不加分号,需要使用者自己抉择,一篇情况下,最好是不在宏定义末尾加上分号,这样出现bug的概率会变低
  • 要警惕使用带副作用的宏参数
#include <stdio.h>
#define MAX(X, Y) ((X)>(Y)?(X):(Y))
int main()
{
    int a = 3;
    int b = 5;
    int c = MAX(a++, b++);
    //((a++)>(b++)?(a++):(b++)),出现加加两次的情况,使用者容易忽略
    printf("%d\n", c);//6
    printf("%d\n", a);//4
    printf("%d\n", b);//7
    return 0;
}
//但是函数在使用的时候就不会出现这种错误,两者之间最大的区别就在于“替换”

(3)命令行定义

有很多C编译器都提供了一种能力,允许直接在命令行中定义符号用于启动编译过程,例如下面这一串代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
    int arr[NUM];//NUM是未定义的,因为不确定运行该代码的机器是否内存足够…
    for (int i = 0; i < NUM; i++)
    {
        arr[i] = i;
    }
    for (int i = 0; i < NUM; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

本次采用的是VS2022自带的编译器cl.exe,其输入命令行的输入命令是:

cl 需要编译的源文件名 -D NUM=100

015+limou+C语言深入知识——(7)编译环境和运行环境以及预处理指令
若是在gcc编译器下,其命令行定义的命令就是:

gcc 需要编译的源文件名 -D NUM=100

这里就不再演示; ,可以在VScode里面进行演示

(4)预编译指令(条件编译指令):#if和#else和#endif等

//单分支条件编译指令
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
//#define NUM1 200
int main()
{
#ifdef NUM1/*或者写成#if也是可以的*/
    printf("定义了NUM1");
#endif
    return 0;
}
//多分支条件编译指令
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
//#define NUM1 200
#define NUM2 100
int main()
{
#ifdef NUM1/*或者写成#if也是可以的*/
    printf("定义了NUM1");
#elif NUM2
    printf("定义了NUM2");
#else
    printf("NUM1和NUM2均未定义\n");
#endif
    return 0;
}
  • 条件编译指令的使用和if-else是类似的,最大的区别是这是预编译指令,是在预编译的时候进行判断的,因此需要注意的是不要放入变量(变量是在程序运行的时候才创建好的,在预编译的过程根本不存在)
  • 条件编译指令的判断语句一般选用常量表达式,而不要使用变量
//正确的写法
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#define NUM1 200
int main()
{
#ifdef NUM1 == 200
    printf("定义了NUM1");
#endif
    return 0;
}
//错误的写法
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
//#define NUM1 200
int main()
{
    int NUM1 = 200;//这在预编译的时候是不可见的!NUM1还未被创建
#ifdef NUM1 == 200
    printf("定义了NUM1");
#endif
    return 0;
}
  • 另外条件编译的写法较多,以下是常见的预处理指令
1.单分支的条件编译
#if 常量表达式,由预处理器求值
    //...
#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

(5)预编译指令(文件包含编译指令):#include

①库文件的包含

#include <stdio.h>
#include <stdlib.h>

②自定义头文件的包含

//在别的地方自定义了一个function_1.h
//在别的地方自定义了一个function_2.h
#include "function.1"
#include "function.2"
//尽管对于头文件也可以使用引号来包含但是查找效率会变低,并且无法区分是库文件还是自定义头文件
//在VS2022里,如果没有在当前源文件路径下找到function_1或function_2头文件的话,就会到标准头文件路径下去找:
//(1)VS是在安装路径中, 一个叫做“include”的文件夹中
//(2)linux是在路径“/usr/include”中

③避免头文件重复(嵌套)包含的方法,这样写头文件会更加规范(而库文件在包含的使用已经经过该处理了)

//写法一:
#ifndef __FUNCTION_H__
    #define __FUNCTION_H__
    //其他代码
#endif

//写法二:
#pragme once
    //其他代码

(6)预编译指令(其他编译指令):#error、#pragma、#line

可以到《C语言深度解剖》的第三章里学习文章来源地址https://www.toymoban.com/news/detail-410319.html

003、宏的编写练习

(1)模拟实现offsetof宏

#define offsetof(StructType, MemberName) (size_t)&(((StructType *)0)->MemberName)

(2)使用宏交换一个二进制数的奇数位和偶数位

#define SwapIntBit(n) (((n) & 0x55555555) << 1 | ((n) & 0xaaaaaaaa) >> 1)

到了这里,关于015+limou+C语言深入知识——(7)编译环境和运行环境以及预处理指令的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 编译和链接(翻译环境:预编译+编译+汇编+链接​、运行环境)

    在ANSI C的任何一种实现中,存在两个不同的环境。​ 第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。​ 第2种是执行环境,它用于实际执行代码。​ VS中编译器:cl.exe ;Linux中目标文件后缀.o  Windows中目标文件.obj  ,链接器 link.exe 那翻译环境是怎么将源

    2024年01月23日
    浏览(36)
  • 深入理解前端字节二进制知识以及相关API

    当前,前端对二进制数据有许多的API可以使用,这丰富了前端对文件数据的处理能力,有了这些能力,就能够对图片等文件的数据进行各种处理。 本文将着重介绍一些前端二进制数据处理相关的API知识,如Blob、File、FileReader、ArrayBuffer、TypeArray、DataView等等。 在介绍各种API之

    2024年02月03日
    浏览(36)
  • Cygwin 配置C/C++编译环境以及如何编译项目

    本文将总结基于 Cygwin 配置C、C++编译环境需要安装哪些包,并给出示例如何进行项目编译。 Cygwin 介绍与安装,请参考:Cygwin 介绍与安装 重新运行 setup-x86_64.exe 程序,步骤与 Cygwin 介绍与安装 基本一致, 重点选择软件包,在选择软件包界面,查看 类别 1. 选择gcc-core、gcc-g+

    2024年02月12日
    浏览(39)
  • “深入探究JVM内部机制:如何实现Java程序的运行环境?“

    标题:深入探究JVM内部机制:如何实现Java程序的运行环境? 摘要:本文将深入探究Java虚拟机(JVM)的内部机制,重点讨论JVM如何实现Java程序的运行环境。我们将从JVM的结构、类加载、内存管理、垃圾回收等方面展开讲解,并通过示例代码具体展示JVM内部机制的运作过程。

    2024年02月11日
    浏览(32)
  • Ubuntu 用gcc/CMakefile编译器 编译、运行c语言程序

    目录 一. 在Ubuntu系统下用c语言编写一个简单的输出hello world 的程序,并编译、运行。 1.1 gcc/g++简介 1.2 c++程序输出 Hello World: 1.3 c语言程序输出 Hello World: 二. 编写一个主程序文件main1.c和一个子程序文件sub1.c,实现函数间的调用 2.1  编写sub1.c 和main1.c 函数 2.1.1 编写sub1.c函数

    2024年02月04日
    浏览(41)
  • Openharmony在RK3568X环境搭建编译及运行-快速上手

    OpenHarmony是由开放原子开源基金会(OpenAtom Foundation)孵化及运营的开源项目, 目标是面向全场景、全连接、全智能时代、基于开源的方式,搭建一个智能终端设备操作系统的框架和平台,促进万物互联产业的繁荣发展 。 针对openharmony板级开发需要准备以下环境: TB-RK3568X开发

    2023年04月19日
    浏览(65)
  • Windows Java JavaFX IntelliJ IDEA 开发环境搭建 创建工程 编译运行 打包分发 自定义运行时

    博文目录 JavaFX 官网 官网 Getting Started with JavaFX JavaFX 是一个开源的下一代客户端应用程序平台,适用于基于 Java 构建的桌面、移动和嵌入式系统。它是许多个人和公司的协作成果,目标是为开发富客户端应用程序生成一个现代、高效且功能齐全的工具包。 JavaFX 主要致力于富

    2024年02月05日
    浏览(142)
  • carla环境安装、运行以及版本切换(Windows)

    本栏目将介绍carla,包括安装、pythonAPI运行、carla的技术点介绍、自行开发脚本玩转carla,以及自定义的控制carla中的车辆,通过carla生成感知数据集等方法。 1. Carla(windows)环境安装、运行 支持操作系统:Windows 渲染引擎版本:Unreal Engine 4.26 硬件配置:显卡4G+,内存16G+(8G也

    2024年02月08日
    浏览(41)
  • VSCode配置C语言编译环境

    一、下载C语言编译器: (1)下载地址:MinGW-w64 - for 32 and 64 bit Windows - Browse /mingw-w64 at SourceForge.net 下载如下的windows版本:  (2)配置环境变量:  二、安装VSCode 三、配置VSCode (1)安装C/C++插件:  (2)配置文件:新建.vscode文件夹,文件夹下新建如下三个文件  1、c_cpp_

    2024年02月10日
    浏览(37)
  • C语言易错知识点十(编译与链接)

    ❀❀❀ 文章由@不准备秃的大伟原创 ❀❀❀ ♪♪♪ 若有转载,请联系博主哦~ ♪♪♪ ❤❤❤ 致力学好编程的宝藏博主,代码兴国!❤❤❤         啊呀,真是许久不见啊~不知道大家有没有想我呢?hh~~  今天上午终于把学校的考试考完了,累鼠了(其实没怎么复习,前段时

    2024年01月21日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包