二三、编译器

这篇具有很好参考价值的文章主要介绍了二三、编译器。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

二三、编译器

1、One Definition Rule

1)转化单元

我们写好的每个源文件(.cpp,.c)将其所包含的头文件(#include <xxx.h>)合并后,称为一个转化单元。

编译器单独的将每一个转化单元生成为对应的对象文件(.obj),对象文件包含了转化单元的机器码和转化单元的引用信息(不在转化单元中定义的对象)。

最后链接器将各个转化单元的对象文件链接起来,生成我们的目标程序。

比如在对象文件A中包含了定义在其它转化单元的引用,那么就去其它转化单元的对象文件中寻找这个引用的定义来建立链接,如果在所有的对象文件中都找不到这个定义,那么就会生成一个链接错误。

2)未定义行为

在编写代码中,C++标准未做规定的行为,称为未定义行为,未定义行为的结果是不确定的,具体不同的编译器下会有不同的效果,比如

c=2*a++ + ++a*6;

这里先算a++还是先算++a就是一个未定义行为,比如

int x = -25602;

x= x>>2;

x的结果在不同的编译器下是不确定的,因为这也属于未定义行为

3)One Definition Rule(ODR)

ODR是一系列规则,而不是一个规则,程序中定义的每个对象都应有着自己的规则;但是基本上来讲任何的变量、函数、类、枚举、模板、概念(C++20)在每个转化单元中都只允许有一个定义;

在整个程序中,非inline的函数或变量(C++17),有且仅能有一个定义

const声明的变量或函数只在当前的源文件中有效,可以在一个项目的不同源文件定义相同的const变量

4)名称的链接属性

程序中的变量、函数、结构等都有着自己的名字,这些名字具有不同的链接属性,链接器就是根据这些链接属性来把各个对象文件链接起来的。链接属性分为以下三种

①内部链接属性:该名称仅仅在本转化单元中有效,如static、const声明的变量、函数

②外部链接属性:该名称在其它转化单元中也有效。通过extern关键字可以定义外部链接属性

③无链接属性:该名称仅仅能够用于该名称的作用域内访问

注:static变量或函数在自己的转化单元有着自己的内存空间,而inline定义的变量只有一个内存地址

2、#define

1)用法一

#define A B          //将标识符A定义为B的别名

#define 整数 int        //将整数替换为int
整数 a{};
//#define实际用法
#include <iostream>
#define _HHHH_ int a  //将_HHHH_ 替换为int a
#define VERSION "V2.0"
int main()
{
	_HHHH_ { 250 };
	std::cout << a << std::endl;
	std::cout << VERSION<<std::endl;
}

2)C++中定义常量的方式

//C++定义常量的方法
const int width{1080};

//C语言中经常通常#define来定义常量
#define width 1080
#define的方式来定义常量存在一个问题,有时候并不安全

3)#define其它写法

#define H  //定义一个标识符H ,代码中的H将会被删除掉
int H a 相当于 int a;

//实际场景应用
#define _in_            //没有实际意义
#define _out_ 
int ave(_in_ int a,_out_ int& b)
{
    return a+b
}

4)取消宏的定义

//语法
#undef H   //

//应用场景
#define _H_
#undef _H_    //删除宏_H_的定义,后面的代码不能使用

注:执行顺序为代码编译的顺序(从上到下),而不是函数调用的顺序

5)定义复杂表达式宏

#define SUM(X,Y) X+Y                 //使用X+Y替换SUM(X,Y)
#define AVE(X,Y) (X+Y)/2             //使用(X+Y)/2替换AVE(X,Y)
#define BIGGER(X,Y) ((X)>(Y)?(X):(Y))

SUM(100,200);
AVE(100,200);
BIGGER(100,200);

//实际应用场景
#define RELEASE(x) delete[] x;x=nullptr

int main()
{
    int* a  = new int[50];
    RELEASE(a);
}

6)定义复杂表达式宏

// #可以将一个标识符参数字符串化
#define SHOW(X) std::cout<<#X          //通过#将X处理成了字符串
SHOW(1234fg);      //相当于std::cout<<"12345fg"

// ##可以连接两个标识符
#define T1(X,Y) void X##Y(){std::cout<<#Y;}
T1(test, 22);
3、namespace

有时候为了方便管理,把相关的函数、变量、结构体等会附加到一个命名空间中

//声明命名空间
namespace t
{
    int value;
}
//访问这个命名空间的变量
t::value

//直接使用命名空间,不推荐
using namespace t;
vlaue=255;

2)全局命名空间

虽有具有链接属性的对象,只要没有定义命名空间,就默认定义在全局命令空间中,全局命名空间中成员的访问不用显示的指定,当局部名称覆盖了全局名称时才需要显示的指定全局命令空间

int a;
::a=250;

3)命名空间的扩展

//第二个htd属于对htd命名空间的扩展,weight和heigth同属于一个命名空间
namespace htd
{
    int weigth{1980};
}
namespace htd
{
    int heigth{1080};
}

4)命名空间的声明

//htd.h
namespace std
{
    extern int height;      //变量声明
    void test();            //函数声明
}

//htd.cpp
#include <iostream>
#include "htd.h"

int htd::heigth{250};      //变量定义
void htd::test()          //函数定义
{
    std::cout<<htd::height;
}

5)命名空间的嵌套

//htd.h
namespace htd
{
    namespace hack               //命名空间的嵌套
    {
        void hackServer();
    }
    
}

//htd.cpp
void htd::hack::hackServer()
{
    ...
}
void htd::sendSms()
{
    ...
}

6)未命名的命名空间

​ 不给命名空间指定名称,将会声明一个未命名的命名空间。未命名的命名空间中声明的内容一律为内部链接属性,包括extern声明的内容,未命名的命名空间仅仅在本转化单元中有效

//t.cpp
void THack()
{
    
}
//x.cpp
namespace
{
    void THack()
	{
    
	}
}	
int main()
{
    THack();
}
//

7)命名空间的别名

namespace htd
{
    void sendSms();
    namespace hack
    {
        void hackServer();
    }
}
namespace hServer=htd::hack;
hServer::hackServer();
4、预处理指令逻辑

所有#开头的代码都是和编译器进行打交道

1)#ifdef

#define _HEIGHT_ 1080       //#ifdef和#endif成对出现
#ifdef _HIGHT_
#else
#endif


//hc.h
#ifdef _HC_        //如果定义了宏_HC_,就执行XXXX里面的代码。如果没有定义,则执行YYYYY
XXXX
#else
YYYYY
#endif

//常见用法
#ifndef _HC_     //如果没有定义了宏_HC_
#define _HC_     //则定义了宏_HC_
#else
#endif
//实际应用场景
#ifdef UNICODE     //如果使用了UNICODE字符集,则使用wchar_t定义变量
wchar_t a;
#else
char a;
#endif

二三、编译器

//通过预处理指令进行版本控制
#define VERSION 101

#if VERSION==100           //当VERSION为100时,执行如下逻辑,否则执行else中的逻辑
	void SendSms()
    {
        
    }
#else
	void SendSms()
    {
        
    }
#endif 

2)#elif

//#elif语法
#define _HEIGHT_ 2080
#if _HEIGHT_ == 1080          //针对每一个分辨率执行不同的逻辑
...
#elif _HEIGHT_ == 720
...
#else
...
#endif
    
//预处理指令可以进行简单的计算//当VERSION为100时,执行如下逻辑,否则执行else中的逻辑
	void SendSms()
    {
        
    }
5、预定义宏

1)标准预定义标识符_fun_

编译器支持ISO C99和ISO C++ 11,即可使用该预定义标识符。用于返回函数的名称

__func__   //返回函数的名称

//用法示例
#include <iostream>

int main()
{
	std::cout << __func__ << std::endl;     //返回函数名称,输出main
}

2)标准预定义宏

编译器支持ISO C99和ISO C++17标准,即可使用以下预定义宏

说明
_DATE_ 返回源文件的编译日期
_TIME_ 返回当前转化单元的转化时间(可理解为代码修改的时间)
_FILE_ 返回源文件的名称
_LINE_ 返回当前的行
__cplusplus 当当前单元为C++时(即.cpp文件时),__cplusplus定义为一个整数,否则不是c++文件
#include <iostream>

int main()
{
	std::cout << __func__ << std::endl;     //返回函数名称,输出main
	std::cout << __DATE__ << std::endl;
	std::cout << __TIME__ << std::endl;
	std::cout << __FILE__ << std::endl;
	std::cout << __LINE__ << std::endl;
	std::cout << __cplusplus << std::endl;
}

二三、编译器

3)MSVC的预定义宏(可理解为微软的VC编译器中,预定义的一些宏)

说明
_CHAR_UNSIGNED 如果char类型为无符号,该宏定义为1,否则为未定义
_COUNTER_ 用于计数,从0开始,每使用一次都会递增1
_DEBUG 如果设置了_DEBUG宏,代表当前为调试状态/lDd /mDd /mTd该宏定义为1,否则为未定义
_FUNCTION_ 返回函数名称 ,但是不包含修饰名
_FUNCDNAME_ 函数名称 包含修饰名
_FUNCSIG_ 包含了函数签名的函数名
_WIN32 当编译为32位ARM,64位ARM,X68或X64定义为1,否则未定义
_WIN64 当编译为64位ARM或x64定义为1,否则未定义。用于区别
_TIMESTAMP_ 最后一次源代码修改的时间和日期
#include <iostream>

int main()
{
#ifdef _CHAR_UNSIGNED  //如果char为无符号类型,可以通过该预定义宏进行检验
	std::cout << "无符号类型";
#endif
	std::cout << "有符号类型" << std::endl;
	std::cout << __COUNTER__ << std::endl;   //代表计数
	std::cout << __COUNTER__ << std::endl;
	std::cout << __COUNTER__ << std::endl;
#ifdef _DEBUG       //代表调试状态
	std::cout << "调试状态" << std::endl;
	std::cout << __FUNCTION__ << std::endl;  //返回函数名,不包含修饰名
	std::cout << __FUNCDNAME__ << std::endl; //返回函数名,包含修饰名
	std::cout << __FUNCSIG__ << std::endl;  //包含函数签名,即调用、约定等信息
	std::cout << __TIMESTAMP__ << std::endl; //最后一次源代码修改的时间和日期

#endif
#ifdef _WIN32  //用于区分Win32架构或者Win64架构
	std::cout << "X86" << std::endl;
#endif // _WIN32

}

只要【项目属性】-【C/C++】-【代码生成】-【运行库】,选择了调试,则_DEBUG就会显示

二三、编译器

二三、编译器

注:上述宏只在微软的VS编辑器才可以使用,其它的编译器无法使用

6、调试

​ 我们编写好程序以后,可能存在一些bug和错误,对于语法上错误,编译器能够直接给出提示,而对于逻辑上的错误,编译器不能够直接发现,调试就是一个找错误和改错误的过程

1)调试建议:为了方便调试,在编程风格上提出如下建议

①功能能模块化就模块化

②使用能够体现出具体意义的函数名和变量名

③使用正常的缩进和代码块

④良好的注释习惯

2)利用集成调试器调调试程序

VS2019继承了一个调试器,可以利用断点、流程跟踪等方式来调试自己的程序

断点就是当程序执行到断点位置,程序就会停下来

3)利用其它调试器

OllyDbg

X96Dbg

WinDbg

4)利用预处理指令来输出调试信息

#define _dbg_i    //先定义一个宏

#ifdef _dbg_i    //如果宏存在,则执行下面的代码块
	std::cout<<"调试信息";
#endif
7、assert

assert宏需要头文件cassert

1)assert语法

//assert语法
assert(bool表达式);   //如果括号内的bool表达式为false,则会调用std::abort()函数,弹出下面的对话框,

二三、编译器

2)关闭assert

//关闭assert
#define NDEBUG  //可以在当前转化单元关闭assert,但是这个定义必须放在#include <cassert>之前

3)static_assert(静态断言)文章来源地址https://www.toymoban.com/news/detail-760377.html

//static_assert用于编译时检查条件
static_assert(bool表达式,"Error information");   //先检查表达式,若表达式为假,则输出后面的错误信息。如果表达式为0,则程序是不进行编译的,此处二点bool表达式只能用于常量

//C++17新语法
static_assert(bool表达式);

到了这里,关于二三、编译器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 【C语言】--编译及编译器

    夫学须静也,才须学也;非学无以广才,非志无以成学 个人主页:【😊个人主页】 系列专栏:【❤️系列专栏】 C语言一直以来都是初入编程的小白们的必修课,作为程序员必学语言之一,C语言自然有属于它的奥秘,接下来就由我来带领大家走进C语言的世界吧🚗🚗🚗 1、

    2024年02月13日
    浏览(54)
  • 前端框架编译器之模板编译

    编译原理:是计算机科学的一个分支,研究如何将 高级程序语言 转换为 计算机可执行的目标代码 的技术和理论。 高级程序语言:Python、Java、JavaScript、TypeScript、C、C++、Go 等。 计算机可执行的目标代码:机器码、汇编语言、字节码、目标代码等。 编译器 (Compiler):是一种将

    2024年04月28日
    浏览(46)
  • 编译原理课程设计--C语言编译器

    源程序1: 源程序1词法分析结果: 与程序1语法分析结果(部分) 源程序1四元式: 源程序1优化后的四元式: action-goto表(部分) 文件目录: (1)掌握语义分析过程,即语法制导翻译过程。 (2)在语法分析的LR分析程序中的基础上添加程序,进行语义分析,生成源程序的四

    2024年02月08日
    浏览(56)
  • openharmony 编译LLVM编译器基础架构

    third_party_llvm-project: 管理员 liwentao_uiw dhy308 huanghuijin (1) 缺少依赖,一次安装好几个依赖 (2) case in的语法识别不了 实际上case in是没有问题的,主要是结尾需要改成Unix结尾

    2024年01月19日
    浏览(45)
  • 【Linux工具】编译器、调式器、项目自动化构建工具以及git的使用(1编译器)

    作者:爱写代码的刚子 时间:2023.6.3 本篇博客主要详细介绍Linux中十分重要的工具:编译器,灵活使用这些工具是Linux中一项必备技能。项目自动化构建工具、调式器、git工具会在下一篇博客中进行介绍。 Linux编译器-gcc/g++使用 gcc编译C语言: g++编译C++: gcc只能编译C语言,而

    2024年02月09日
    浏览(67)
  • Python 编译器

    什么是编译器 Python 编译器的发展历程 Python 编译器的类型 常见的 Python 编译器 如何选择 Python 编译器 Python Logo 编译器是将源代码转换成可执行代码的程序。Python 作为一门高级编程语言,需要借助编译器将代码转换成机器语言,以便计算机识别并执行。 早期版本的 CPython 解释

    2024年02月11日
    浏览(43)
  • 提速Rust编译器!

    Nethercote是一位研究Rust编译器的软件工程师。最近,他正在探索如何提升Rust编译器的性能,在他的博客文章中介绍了Rust编译器是如何将代码分割成代码生成单元(CGU)的以及rustc的性能加速。 他解释了不同数量和大小的CGU之间的权衡以及Rustc是如何使用LLVM并行化代码生成和优

    2024年02月13日
    浏览(32)
  • MSVC编译器介绍

    与Linux系列操作系统不同,Windows原生环境不提供类似 gcc , Clang 的C/C++语言 源程序编译运行工具链 。运行在Windows上的IDE(集成开发环境),比如CodeBlocks之类,一般都使用 MinGW ( Minimalist GNU for Windows ) 配置模拟Linux下的开发环境来进行Windows下的开发。 但是 在Windows下,与开

    2024年02月02日
    浏览(45)
  • 编译器的过度优化

    编译器在进行优化的时候,可能为了效率而交换不相关的两条相邻指令的执行顺序。也就是指令重排,这也就引发了一些问题,下面就带大家看两个经典的问题。 第一个例子来自单例模式的双加锁,下面是典型的双加锁的单例模式代码: 上面的代码看起来没问题,并且采用

    2023年04月21日
    浏览(31)
  • 交叉编译器介绍

    简介 ​ 要在 X86 的电脑上编译出能够在 Arm 上运行的程序,我们必须明确告诉编译器,编译生成的可执行文件需要以 Arm 指令集的标准编码。开发者们为不同的芯片开发了不同的编译器,比如针对 Arm 平台的 arm-linux-gcc,针对 mips 平台的 mips-linux-gnu-gcc,这些编译器都是基于 G

    2024年02月06日
    浏览(60)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包