为什么不自动假设#pragma?

Joh*_*che 75 c c++ pragma

告诉编译器只包含一次文件有什么意义?默认情况下它不会有意义吗?是否有任何理由多次包含单个文件?为什么不假设呢?是与特定硬件有关吗?

Max*_*hof 80

这里有多个相关问题:

  • 为什么#pragma once不自动执行?
    因为在某些情况下您希望多次包含文件.

  • 为什么要多次包含文件?
    其他答案(Boost.Preprocessor,X-Macros,包括数据文件)中给出了几个原因.我想添加一个"避免代码重复"的特定示例:OpenFOAM鼓励一种风格,其中#include功能中的点点滴滴是一个常见的概念.例如,参见讨论.

  • 好的,但是为什么它不是选择退出的默认值?
    因为它实际上并未由标准指定.#pragma根据定义,特定于实现的扩展.

  • 为什么还#pragma once没有成为一个标准化的功能(因为它得到广泛支持)?
    因为以平台无关的方式固定"同一个文件"实际上是非常困难的.有关更多信息,请参阅此答案.

  • 并非所有pragma都是特定于实现的扩展.例如[`#pragma STDC` family](https://en.cppreference.com/w/c/preprocessor/impl#Standard_pragmas).但它们都控制着实现定义的行为. (5认同)
  • 特别是,[参见这个例子](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52566)对于一个"pragma once"失败但包含警卫的情况会有效.按位置识别文件也不起作用,因为有时同一个文件在项目中多次出现(例如,你有2个子模块,它们的标题中都包含一个只有标题的库,并检查它们自己的副本) (4认同)
  • @ user4581301这个答案夸大了pragma的问题一次,并没有考虑因为包括警卫而引起的麻烦.在这两种情况下都需要一些纪律.对于包含保护,必须确保使用不会在其他文件中使用的名称(这将在文件副本修改后发生).有了pragma一次,就必须决定它的文件唯一正确的位置,毕竟这是一件好事. (3认同)
  • @Mehrdad:你是否真的建议编译器写入源文件!?如果编译器看到`#ifndef XX`,它必须知道在相应的`#endif`之后是否有任何内容,直到它读取整个文件.一个编译器,跟踪最外面的`#ifndef`是否包含整个文件,并记下它检查的宏可能能够避免重新扫描文件,但指示*说*在当前点之后没有任何重要的东西看起来似乎比依靠编译器记住这些东西更好. (3认同)

Jes*_*uhl 36

您可以在文件中的#include 任何位置使用,而不仅仅是在全局范围内 - 例如,在函数内部(如果需要,可以多次使用).当然,丑陋和不好的风格,但可能和偶尔明智(取决于你包括的文件).如果#include只是一次性事情那么那就行不通了.#include毕竟,只是做了愚蠢的文本替换(cut'n'paste),并不是你所包含的所有内容都必须是头文件.例如,您可能#include包含一个包含自动生成数据的文件,其中包含用于初始化a的原始数据std::vector.喜欢

std::vector<int> data = {
#include "my_generated_data.txt"
}
Run Code Online (Sandbox Code Playgroud)

并且"my_generated_data.txt"是编译期间构建系统生成的内容.

或者也许我懒惰/愚蠢/愚蠢并把它放在一个文件中(非常人为的例子):

const noexcept;
Run Code Online (Sandbox Code Playgroud)

然后我做

class foo {
    void f1()
    #include "stupid.file"
    int f2(int)
    #include "stupid.file"
};
Run Code Online (Sandbox Code Playgroud)

另一个稍微不那么做作的例子是一个源文件,其中许多函数需要在命名空间中使用大量类型,但是你不想只是说using namespace foo;全局,因为这会用很多其他东西来调用全局命名空间你不想要.所以你创建一个包含的文件"foo"

using std::vector;
using std::array;
using std::rotate;
... You get the idea ...
Run Code Online (Sandbox Code Playgroud)

然后在源文件中执行此操作

void f1() {
#include "foo" // needs "stuff"
}

void f2() {
    // Doesn't need "stuff"
}

void f3() {
#include "foo" // also needs "stuff"
}
Run Code Online (Sandbox Code Playgroud)

注意:我不是在提倡做这样的事情.但是有可能在一些代码库中完成,我不明白为什么不应该允许它.它确实有它的用途.

也可能是您包含的文件的行为有所不同,具体取决于某些宏的值#define.因此,您可能希望在首次更改某个值后将文件包含在多个位置,以便在源文件的不同部分中获得不同的行为.

  • @PSkocik如果我在每个include之前更改`#define`s的值会改变包含文件的行为,那么我可能需要多次包含它以在源文件的不同部分中获取这些不同的行为. (8认同)
  • @PSkocik 但也许我*需要*不止一次地包含它。为什么我不能? (2认同)
  • @JesperJuhl 这就是重点。您不会_需要_多次包含它。您目前可以选择,但替代方案也不会更糟,如果有的话。 (2认同)

PSk*_*cik 26

包括多次可用,例如,使用X-macro技术:

data.inc:

X(ONE)
X(TWO)
X(THREE)
Run Code Online (Sandbox Code Playgroud)

use_data_inc_twice.c

enum data_e { 
#define X(V) V,
   #include "data.inc"
#undef X
};
char const* data_e__strings[]={
#define X(V) [V]=#V,
   #include "data.inc"
#undef X
};
Run Code Online (Sandbox Code Playgroud)

我不知道任何其他用途.

  • @JohnnyCache:该示例是X-macros如何工作的简化版本.请阅读链接; 在某些情况下,它们对表格数据的复杂操作非常有用.在X-macros的任何重要用法中,你都无法"将这些定义包含在文件中". (2认同)
  • @Johnny - 是的 - 一个很好的理由是确保一致性(当你只有几十个元素时很难手工完成,更不用说数百个元素). (2认同)

T.E*_*.D. 19

您似乎在假设即使语言中存在的"#include"功能的目的是为了将程序分解为多个编译单元提供支持的情况下运行.那是不对的.

它可以执行该角色,但这不是其预期目的.C 最初是为比重新实现Unix的PDP-11 Macro-11 Assembly 开发的稍高级语言.它有一个宏预处理器,因为这是Macro-11的一个功能.它能够以文本方式包含来自另一个文件的宏,因为这是Macro-11的一个特性,它们正在移植到新的C编译器的现有Unix已经使用了.

现在事实证明"#include" 对于将代码分成编译单元很有用,因为(可以说)有点像黑客.然而,这个黑客存在意味着它成为了在C.做到这一点的方式存在意味着没有新的方法不断的事实的方式,其实需要要创建提供此功能,所以没有什么更安全(如:不容易受到多-inclusion)曾经创造过.因为它已经在C中,所以它被复制到C++以及C语言和惯用语的其余部分.

有一个建议为C++提供一个合适的模块系统,这样这个45岁的预处理器黑客最终可以免除.我不知道这是多么迫近.我已经听说它已经有十多年了.

  • 像往常一样,要了解C和C++,您需要了解它们的历史. (5认同)
  • @DavisHerring - 是的,但哪个二月? (5认同)

Ser*_*eyA 10

不,这将显着阻碍例如图书馆作者可用的选项.例如,Boost.Preprocessor允许使用预处理器循环,实现这些循环的唯一方法是通过同一文件的多个包含.

Boost.Preprocessor是许多非常有用的库的构建块.


Gra*_*ham 8

在我主要使用的产品的固件中,我们需要能够指定应在内存中分配函数和全局/静态变量的位置.实时处理需要存储在芯片上的L1存储器中,以便处理器可以直接,快速地访问它.不太重要的处理可以在芯片上的L2存储器中进行.任何不需要特别及时处理的东西都可以存在于外部DDR中并通过缓存,因为如果速度稍慢则没关系.

分配到哪里的#pragma是一条很长的,非平凡的线.这很容易弄错.犯错的效果将是代码/数据将被悄悄放进默认(DDR)内存,并且效果可能的闭环控制停止无缘无故,很容易看到工作.

所以我使用include文件,其中只包含该pragma.我的代码现在看起来像这样.

头文件......

#ifndef HEADERFILE_H
#define HEADERFILE_H

#include "set_fast_storage.h"

/* Declare variables */

#include "set_slow_storage.h"

/* Declare functions for initialisation on startup */

#include "set_fast_storage.h"

/* Declare functions for real-time processing */

#include "set_storage_default.h"

#endif
Run Code Online (Sandbox Code Playgroud)

来源......

#include "headerfile.h"

#include "set_fast_storage.h"

/* Define variables */

#include "set_slow_storage.h"

/* Define functions for initialisation on startup */

#include "set_fast_storage.h"

/* Define functions for real-time processing */
Run Code Online (Sandbox Code Playgroud)

你会注意到同一个文件的多个包含,即使只是在标题中.如果我现在输入错误,编译器会告诉我它找不到包含文件"set_fat_storage.h",我可以轻松修复它.

因此,在回答您的问题时,这是一个真实,实用的例子,说明需要多个包含.

  • 我会说你的用例是`_Pragma`指令的一个激励性例子.现在可以从常规宏扩展相同的编译指示.所以不需要包括多次. (3认同)