简体   繁体   English

禁止在编译单元中包含不兼容的标头

[英]Prohibit inclusion of incompatible headers across compilation units

This is a theoretical question: Assume you have a library and it has two headers. 这是一个理论问题:假设你有一个库,它有两个标题。 Is there any possibility with C++ or with preprocessor macros or a combination of both to achieve the following behaviour: 是否有可能使用C ++或预处理器宏或两者的组合来实现以下行为:

  1. Dependent projects can include Header 1 or Header 2 in any number of compilation units without error. 依赖项目可以包含任意数量的编译单元中的标题1 标题2而不会出错。
  2. Dependent Projects cannot include both Headers even if its accross different compilation units. 依赖项目不能同时包含两个标题,即使它不同的编译单元。

I'd like to have some construct that would result in some kind of error (eg a linker error) for the 2nd case. 我想有一些构造会导致第二种情况出现某种错误(例如链接器错误)。 I does not need to give a nice error message, I just want to prohibt inclusion of two incompatible headers in the same dependent project. 我不需要提供一个很好的错误消息,我只想禁止在同一依赖项目中包含两个不兼容的标头。 Is that possible? 那可能吗?

EXAMPLE: 例:

Header1.h 那么header1.h

// type definitions for foo, Version 1
...

Header2.h Header2.h

// type definitions for foo, Version 2
...

Scenario 1: 场景1:

// (linker) error, versions do not match
CompilationUnit1.cpp <-- Header1.h
CompilationUnit2.cpp <-- Header2.h

Scenario 2: 场景2:

// ok, versions match
CompilationUnit1.cpp <-- Header2.h
CompilationUnit2.cpp <-- Header2.h

not sure if you are talking about generic headers of a library, for example vec.h and mat.h, and for some reason you want to express rules to avoid to have: 不确定你是否在讨论库的通用头文件,例如vec.h和mat.h,并且出于某种原因你想表达规则以避免:

#include <vec.h>
#include <mat.h>

If that is the case I don't think that is possible, on the other hand if you are talking about same header from different library version, like vec_2_0.h , vec_2_1.h I would talke the problem differently (well first of all if I could name the headers I would not write the version on the header itself). 如果是这种情况我不认为这是可能的,另一方面如果你在讨论来自不同库版本的相同标题,比如vec_2_0.h,vec_2_1.h我会以不同的方式解决问题(首先,如果我可以命名标题,我不会在标题本身上编写版本)。

The way I would tackle is by separating the includes in the folder structure example: 我要解决的方法是在文件夹结构示例中分离包含:

mathlib:

-----> mathlib_2_0
---------> includes
----> mathlib_2_1
-------->  includes

Then you can enforce at project settings to avoid picking one over the other, rather then trying to fix that in the file itself. 然后,您可以在项目设置中强制执行以避免选择一个而不是另一个,而不是尝试在文件本身中修复它。

Maybe what you can do (but I think is ugly) is to wrap all the possible header include in a macro and use a define version to have the processor leaving only the correct one. 也许你可以做的(但我认为很难看)是将所有可能的头包含在宏中并使用定义版本让处理器只留下正确的一个。 Example: 例:

Compiler flag -DMathVersion 2_0

The macro: 宏:

INCLUDE_VECTOR_HEADER()
{
#if MathVersion == 2_0
#include <vec_2_0.h>
#elif MathVersion == 2_1
#include <vec_2_1.h>
#endif
}

But I think is way overkill and a bit ugly, maybe can be generalized in having the version as parameter of the macro. 但我认为这样做有点过分,有点难看,也许可以将版本作为宏的参数进行推广。 I would not go down that road personally. 我不会亲自去那条路。 PS: All is pseudo code just to give an idea PS:所有都是伪代码只是为了给出一个想法

A run time check is doable language-only. 运行时检查只能使用语言。 (In another post I'll suggest a number of build-time checks.) (在另一篇文章中,我将建议一些构建时间检查。)

This is not elegant. 这不优雅。 I have a nagging feeling that somebody will come up with a 2-liner for this. 我有一种唠叨的感觉,有人会为此想出一个2线。 But for what it's worth. 但是它的价值。

Each header defines a class which will be instantiated once for each translation unit when the program starts. 每个头定义一个类,当程序启动时,该类将为每个翻译单元实例化一次。 (We need a class because we need to run ctor code in order to check a value.) The ctor of each class reads a global sentinel and checks whether it has the wrong magic value (it will be 0 initially as a global). (我们需要一个类,因为我们需要运行ctor代码来检查一个值。)每个类的ctor读取一个全局sentinel并检查它是否具有错误的魔术值(它最初将为0作为全局值)。 If not, it assigns its own. 如果没有,它会分配自己的。 It does not rely on the initialization order of the static objects. 它不依赖于静态对象的初始化顺序。 I am not sure whether we need to protect sentinel from concurrent access here; 我不确定我们是否需要保护哨兵免遭并发访问; I hope not. 我希望不是。

lib.cpp (your library): lib.cpp(你的库):

int sentinel;
// other lib stuff
// ...

f1.h (one of the two headers): f1.h(两个标题之一):

#include<iostream>
#include<cstdlib>
using namespace std;

extern int sentinel;

struct f1duplGuard
{
    enum { f1=0xf1, f2= 0xf2 };

    f1duplGuard() 
    { 
        cout << "f1 duplGuard() " << endl;
        if( ::sentinel == f2 ) 
        {
            cerr << "f1: include violation -- must be a f2 somewhere" << endl;
            exit(1); 
        }
        sentinel = f1;
    }
};

static f1duplGuard dg;

f2.h (the other header -- the moirror image with a different constant): f2.h(另一个标题 - 具有不同常量的moirror图像):

#include<iostream>
#include<cstdlib>
using namespace std;

extern int sentinel;

struct f2duplGuard
{
    enum { f1=0xf1, f2= 0xf2 };

    f2duplGuard() 
    { 
        cout << "f2 duplGuard() " << endl;

        if( ::sentinel == f1 ) 
        {
            cerr << "f2: include violation -- must be a f1 somewhere" << endl;
            exit(1); 
        }
        sentinel = f2;
    }
};

static f2duplGuard dg;

One TU using the lib, including one of the two f headers 一个TU使用lib,包括两个f头之一

#include <iostream>
#include "f2.h"      // changing this to f1.h fails at run time

using namespace std;


void f(void)
{
    cout << "second.cpp, f()" << endl;
}

The second TU using the lib, including an f header, too (with main): 使用lib的第二个TU,包括一个f头(带有main):

#include <iostream>
#include "f2.h"

extern void f();

using namespace std;

int main(void)
{
    f();
    return 0;
}

Phew. 唷。 If you change one of the two includes to the other header, you trigger an error message and exit. 如果将两个包含中的一个更改为另一个标头,则会触发错误消息并退出。

Sample session (without the error): 示例会话(没有错误):

$ g++ -O0 -std=c++14  -o dupl-static -Wall second.cpp dupl-static.cpp lib.cpp && ./dupl-static
f2 duplGuard()
f2 duplGuard()
second.cpp, f()

Sample session with error: 示例会话有错误:

 cat dupl-static.cpp && g++ -std=c++14  -o dupl-static -Wall second.cpp dupl-static.cpp lib.cpp && ./dupl-static
#include <iostream>
#include "f1.h"

extern void f();

using namespace std;

int main(void)
{
        f();
}
f1 duplGuard()
f2 duplGuard()
f2: include violation -- must be a f1 somewhere

You're working with Visual C++ then it can be done (but be warned that it's not portable and you will get warnings instead of errors unless you use /WX ). 您正在使用Visual C ++然后它可以完成 (但要注意它不是可移植的,除非您使用/WX否则您将收到警告而不是错误)。 With GCC (maybe, I didn't try) you may use somehow #pragma weak . 使用GCC(也许,我没有尝试)你可能会以某种方式使用#pragma weak

Because each compilation unit is compiled separately then you have to find something at linker level (exporting a function, changing a setting). 因为每个编译单元都是单独编译的,所以你必须在链接器级别找到一些东西 (导出一个函数,改变一个设置)。 Easiest way I found is to declare a section and allocate a dummy variable on it. 我发现最简单的方法是声明一个部分并在其上分配一个虚拟变量。 If this section is declared with different attributes on your headers then linker will complain. 如果在标题上声明此部分具有不同的属性,那么链接器将会抱怨。

You have to add this to Header1.h : 你必须将它添加到Header1.h

#pragma section("mylib_priv_impl_section",read)
__declspec(allocate("mylib_priv_impl_section")) static int mylib_priv_impl_var = 0;

And then in Header2.h : 然后在Header2.h中

#pragma section("mylib_priv_impl_section",read,write)
__declspec(allocate("mylib_priv_impl_section")) static int mylib_priv_impl_var = 0;

Now when you compile and link you will get this warning: 现在,当您编译和链接时,您将收到此警告:

multiple 'mylib_priv_impl_section' sections found with different attributes (C0300040) 找到具有不同属性的多个'mylib_priv_impl_section'部分(C0300040)

Because we declared same section with different attributes ( read in Header1 and read+write in Header2). 因为我们宣称具有不同属性的相同部分( read在头1和read+write在头2)。 Unfortunately it's just a warning (you may use section name to give some useful diagnostic message) and to stop compilation you must specify /WX (unfortunately ignored by #pragma comment(linker, "/WX") ). 不幸的是,它只是一个警告(您可以使用节名称来提供一些有用的诊断消息)并且要停止编译,您必须指定/WX (遗憾的是#pragma comment(linker, "/WX")忽略)。

Note that we need that dummy variable otherwise our declared section (if unused) will be simply ignored. 请注意,我们需要该虚拟变量,否则我们声明的部分(如果未使用)将被忽略。 Nothing else but our dummy variable will be placed in that section (see also Scope of __declspec allocations ). 除了我们的虚拟变量之外,其他任何内容都将放在该部分中(另请参阅__declspec分配的范围 )。


Note that if a preprocessor macro is viable then also a solution similar to what Marco Giordano suggested will work smoothly. 请注意,如果预处理器宏可行,那么类似于Marco Giordano建议的解决方案也将顺利运行。 I'd just change the way it work to throw an error if you're including Header1 but you set you want to use Version2 (and vice-versa) instead of having a macro for inclusion. 如果你包含Header1,我只是改变它的工作方式来抛出错误,但你设置你想要使用Version2(反之亦然)而不是包含宏。 Something like this (in Header1 and specular in Header2): 像这样的东西(在Header1和Header2中的镜面反射):

#if MyLibVersion != 1
#error You are including version 1 headers, please set MyLibVersion accordingly.
#endif

I see several ways to do that at build time (but with the help of mechanisms beyond just the compiler/linker). 我在构建时看到了几种方法(但在除了编译器/链接器之外的机制的帮助下)。

Pre-compile: Use gcc's dependency generator. 预编译: 使用gcc的依赖生成器。

gcc has an option to walk the include tree for each file on the command line and list its dependencies, among them the directly or indirectly included header files. gcc可以选择在命令行中为每个文件遍历包含树,并列出其依赖项,其中包括直接或间接包含的头文件。 Obviously this needs gcc, but it won't actually compile anything, so it could be a pre-build command in VS with a cygwin gcc. 显然这需要gcc,但它实际上不会编译任何东西,所以它可能是VS中带有cygwin gcc的预构建命令。 Probably only minimal configuration is necessary because the option -MG lets gcc handle "missing" headers gracefully. 可能只需要最小配置,因为选项-MG允许gcc优雅地处理“缺失”头文件。 (In order to follow nested includes it will be necessary to use -I in order to define include paths.) (为了遵循嵌套的包含,有必要使用-I来定义包含路径。)

As an example let's assume we have three files. 举个例子,假设我们有三个文件。
1.c: 1.C:

#include "header1.h"

2.c : 2.c:

#include <stdio.h>
#include "2.h"

and 2.h: 和2.h:

#include "header2.h"

Sample session: 示例会话:

$ ls && echo "-------" && gcc -MM -MG *.c
1.c  2.c  2.h
-------
1.o: 1.c header1.h
2.o: 2.c 2.h header2.h

This can be simply grepped for the header names. 这可以简单地用于标题名称。

Compile time: 编译时间:

Define a C preprocessor token during the build and check it in the headers 在构建期间定义C预处理程序令牌并在标头中检查它

Agree on two well-known tokens, of which exactly one will be defined by the build environment for any build; 同意两个众所周知的令牌,其中一个将由构建环境为任何构建定义; the headers check against them with an #ifdef . 标题用#ifdef检查它们。 For example, header1.h could contain the line 例如, header1.h可以包含该行

#ifdef USE_HEADER2
#   error "Using header1.h although USE_HEADER2 is defined"
#endif

In a make environment one could define USE_HEADER2 by passing a -D option, like make -DUSE_HEADER_1 ...` or the like. 在make环境中,可以通过传递-D选项来定义USE_HEADER2 ,例如make -DUSE_HEADER_1 ...`等。 In Visual Studio one would define the symbol in the Build properties of the project. 在Visual Studio中,可以在项目的“构建”属性中定义符号。

This will not work for incremental builds, because between builds the setting might change. 这对增量构建不起作用,因为在构建之间,设置可能会更改。 That is surely a source of errors; 这肯定是错误的根源; only doing complete rebuilds as a remedy is prohibitive for large projects. 只做完全重建作为补救措施对大型项目来说是禁止的。

Post-Compile: Define a static string in each header and grep the binaries for it 编译后:在每个标头中定义一个静态字符串,并为其添加二进制文件

This is inspired by @MSalters' suggestion in the comments, but possibly a bit simpler. 这是受到@MSalters在评论中的建议的启发,但可能有点简单。 Define a distinct static string in each header. 在每个标头中定义不同的静态字符串。 For header1.h , that could be 对于header1.h ,可能是

volatile static char str[] = "HEADER_1_INCLUDED";

(Without the volatile the compiler would dismiss the never-used string when built with optimization. This could be implementation specific. I used gcc 4.9.3.) (如果没有volatile ,编译器会在使用优化构建时忽略从未使用的字符串。这可能是特定于实现的。我使用了gcc 4.9.3。)

After a build you just check all library and object files for both strings and fail if both are there: 在构建之后,您只需检查两个字符串的所有库和目标文件,如果两者都存在则会失败:

 if grep -q  "HEADER_1_INCLUDED" *.o *.a *.lib *.dll &&  grep -q  "HEADER_2_INCLUDED" *.o *.a *.lib *.dll; 
 then handle_error; 
 fi

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM