Linkerist / blog

Linkerist's blog.
Apache License 2.0
18 stars 0 forks source link

The _Pragma("once") vs include guards #28

Open Linkerist opened 3 years ago

Linkerist commented 3 years ago

We can use the _Pragma directive for header file guard.

example:

#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_

change to

_Pragma("once")

There are several factors to support the lowcase file/directory name.

portability Cross-platform problems will not occur, compiler implement standard, they will support it. readability Generally, if you just want to do some guard, you should not care about the name and the directory of this file. and the naming of the guard is yet another big question, nonstandard things always end up in a fight. convenience just copy & paste for the _Pragma("once"), here and there.

Computer industry set the standards, so does our company.

feel free to refer to tencent cplusplus code standard, “2.2.【必须】 头文件保护”: https://git.code.oa.com/standards/cpp#22%E5%BF%85%E9%A1%BB-%E5%A4%B4%E6%96%87%E4%BB%B6%E4%BF%9D%E6%8A%A4

image

More importantly, the _Pragma directive is the standard level in C++, instead of the a specific compiler extension like GCC's # pragma, which maximizes stability and compatibility.

see:


_Pragma操作符

在C/C++标准中,#pragma是一条预处理的指令(preprocessor directive)。简单地说,#pragma是用来向编译器传达语言标准以外的一些信息。举个简单的例子,如果我们在代码的头文件中定义了以下语句:

#pragma once

要达到与上例#pragma类似的效果,则只需要如下代码即可。

_Pragma("once");

而相比预处理指令#pragma,由于_Pragma是一个操作符,因此可以嵌套在宏中。我们可以看看下面这个例子:

#define CONCAT(x) PRAGMA(concat on #x)
#define PRAGMA(x) _Pragma(#x)
CONCAT( ..\concat.dir )

这里,CONCAT( ..concat.dir )最终会产生_Pragma(concat on "..concat.dir")这样的效果。而#pragma则不能在宏中展开,因此从灵活性上来讲,标准的 _Pragma具有更大的灵活性

注意,为什么强调“标准”,我指的是

_Pragma("once")

而不是

#pragma once

以上,可以看出,不管从可移植性、可读性、便利性,还是规范角度看,_Pragma("once")都有优势。使用新标准可以提高开发效率,用最自然的思维方式、最简单的代码,表达大量内容。

因为前者是语言层面的标准,而后者是编译器的扩展,可能在标准通过_Pragma操作符之前,GCC就已经实现了#pragma,这个时候,windows,macOS系统的编译器可能是不认识#pragma的,他可能是别的foo, bar。但是,标准通过之后,所有编译器都将遵循标准。

所以,使用标准,会是最通用的方式,也是我们的方式。

详见n3690.pdf:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3690.pdf

image


不过有观点认为,我们不该使用_Pragma("once"),因为编译器的实现有bug。(https://stackoverflow.com/questions/1143936/pragma-once-vs-include-guards

我们确立一个标准,提高开发效率的同时,也要确认该标准是否稳定可用,为了防止引入未知的问题。

网上的资料不多,就直接看了下内核、编译器的实现。

早期存在的问题在于,比如foo.h -> /path/to/foo.h链接会共享相同的inode,导致编译器无法区分。(详见附录“内核实现分析”)

在问题相关的上下游,有如下若干事实: 编译器层面,gcc在3.4.0版本的时候已经修复了上述问题。(详见附录“编译器实现分析”) 开发者层面,绝大多数项目都不会存在上述问题。

这让我们相信标准,且相信标准的实现。

极端假设,即使编译器仍存在这个所谓的bug,并且我们刻意触发了,那么会不会有严重问题呢?答案是否定的。因为,它和ifndef造成的macro冲突问题是同一级别的,而且更容易在最早期的编译阶段被发现。换言之,既然你都可以接受ifndef,那么为什么不能接受_Pragma("once")呢,后者即便是在最邪恶的情况下,也不会比前者错得更多。

结论:我们可以使用标准的_Pragma操作符

大家已经将这个结论,作为我们的共识。在代码规范中已更新。

具体的编码,可以直接遵循规范:

如果存在ifndef头文件保护,那么麻烦进行如下替换:

#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_

改成

_Pragma("once")

如果不存在,请直接使用

_Pragma("once")

内核实现分析(linux-5.7.0):

image

编译器实现分析(gcc-9.2.0):

image