简体   繁体   English

跨模块* / c,c ++的编译时断言

[英]compile time assertions *across modules* / c,c++

recently i discovered in a relatively large project, that ugly runtime crashes occurred because various headers were included in different order in different cpp files. 最近,我在一个相对较大的项目中发现,丑陋的运行时崩溃是因为在不同的cpp文件中以不同的顺序包含了各种标头。

These headers included #pragma pack - and these pragmas were sometimes not 'closed' ( i mean, set back to the compiler default #pragma pack() ) - resulting in different object layouts in different object files. 这些标头包含#pragma pack-有时未“关闭”这些pragma(我是说,将其设置回编译器默认的#pragma pack())-导致不同对象文件中的对象布局不同。 No wonder the application crashed when it accessed struct members being created in one module and passed to another module. 难怪当应用程序访问在一个模块中创建并传递到另一个模块的结构成员时崩溃。 Or derived classes accessing members from base classes. 或派生类从基类访问成员。

Since i like the idea to create a more general debugging and assertion strategy from every bug i find, i would really like to assert that object layouts are always and everywhere the same. 由于我喜欢从发现的每个错误中创建更通用的调试和断言策略的想法,因此我真的想断言对象布局始终且在各处都是相同的。

So it would be easy to assert 所以很容易断言

ASSERT( offsetof(membervar) == 4 ) ASSERT(offsetof(membervar)== 4)

But this would not catch a different layout in another module - or require manual updates whenever the struct layout changes .. so my favourite idea would be something like 但这不会在另一个模块中捕获不同的布局-或每当结构布局更改时都需要手动更新。

ASSERT( offsetof(membervar) == offsetof(othermodule_membervar) ) ASSERT(offsetof(membervar)== offsetof(othermodule_membervar))

Would this be possible with an assertion? 一个断言有可能吗? Or is this a case for a unit test? 还是单元测试的情况?

Thanks, H 谢谢,H

ASSERT( offsetof(membervar) == offsetof(othermodule_membervar) ) ASSERT(offsetof(membervar)== offsetof(othermodule_membervar))

I can't see way to make this technically possible. 我看不到使技术上可行的方法。 Further, even if it was phyiscally possible, it isn't practical. 此外,即使在物理上可行,这也不实用。 You'd need an assert for every pair of source files: 您需要为每对源文件声明一个断言:

ASSERT( offsetof(A.c::MyClass.membervar) == offsetof(B.c::MyClass.membervar) )
ASSERT( offsetof(A.c::MyClass.membervar) == offsetof(C.c::MyClass.membervar) )
ASSERT( offsetof(A.c::MyClass.membervar) == offsetof(D.c::MyClass.membervar) )
ASSERT( offsetof(B.c::MyClass.membervar) == offsetof(C.c::MyClass.membervar) )
ASSERT( offsetof(B.c::MyClass.membervar) == offsetof(D.c::MyClass.membervar) )

etc 等等

You might be able to get away with this by asserting the sizeof(class) in different files. 您可以通过在不同文件中声明sizeof(class)来解决此问题。 If the packing is causing the size of the object to be smaller, than I would expect that sizeof() would show that up. 如果打包导致对象的大小变小,则比我预期的sizeof()会大。

You could also do this as a static assert using C++0x's static assert, or Boost's (or a handrolled one of course) 您也可以使用C ++ 0x的静态断言或Boost的静态断言(或手动滚动的静态断言)来完成此操作

On the part of not wanting to do this in every file, I would recommend putting together a header file that includes all the headers you're worried about, and the static_asserts. 如果不想在每个文件中都这样做,我建议将一个头文件放在一起,其中包括您担心的所有头文件和static_asserts。

Personally though, I'd just recommend searching through the code base over the list of pragmas and fix them. 不过,就我个人而言,我只建议在编译指示列表中搜索代码库并进行修复。

Wendy, 温迪,

In Win32, there are single functions that can populate different versions of a given struct. 在Win32中,有单个函数可以填充给定结构的不同版本。 Over the years, the FOOBAR struct might have new features added to it, so they create a FOOBAR2 or FOOBAREX. 多年来,FOOBAR结构可能已添加了新功能,因此他们创建了FOOBAR2或FOOBAREX。 In some cases there are more than two versions. 在某些情况下,会有两个以上的版本。

Anyway, the way they handle this is to have the caller pass in sizeof(theStruct) in addition to the pointer to the struct: 无论如何,他们处理此问题的方法是让调用者除了传递指向结构的指针外,还传递sizeof(theStruct)

FOOBAREX foobarex = {0};
long lResult = SomeWin32Api(sizeof(foobarex), &foobarex);

Within the implementation of SomWin32Api() , they check the first parameter and determine which version of the struct they're dealing with. SomWin32Api()的实现中,他们检查第一个参数并确定要处理的结构的版本。

You could do something similar in a debug build to assure that the caller and callee agree on the size of the struct being referred to, and assert if the value doesn't match the expected size. 您可以在调试版本中执行类似的操作,以确保调用方和被调用方在所引用结构的大小上达成一致,并断言该值与预期大小不匹配。 With macros, you might even be able to automate/hide this so that it only happens in a debug build. 使用宏,您甚至可以自动/隐藏它,使其仅在调试版本中发生。

Unfortunately, this is a run-time check and not a compile-time check... 不幸的是,这是运行时检查,而不是编译时检查...

What you want isn't directly possible as such. 这样就无法直接实现您想要的一切。 If you're using VC++, the following may be of interest: 如果您使用的是VC ++,可能会感兴趣以下内容:

http://blogs.msdn.com/vcblog/archive/2007/05/17/diagnosing-hidden-odr-violations-in-visual-c-and-fixing-lnk2022.aspx http://blogs.msdn.com/vcblog/archive/2007/05/17/diagnosing-hidden-odr-violations-in-visual-c-and-fixing-lnk2022.aspx

There's probably scope to create some way of semi-automating the process it describes, collating the output and cross-referencing. 可能存在创造某种方式来半自动化其描述的过程,整理输出和交叉引用的范围。

To detect this sort of problem somewhat more automatically, the following occurs to me. 为了更自动地检测到此类问题,我想到了以下事情。 Create a file that defines a struct that will have a particular size with the designated default packing amount, but a different size with different pack values. 创建一个定义结构的文件,该结构将具有指定的默认包装量的特定大小,但具有不同包装值的不同大小。 Also include some kind of static assert that its size is correct. 还包括某种静态断言,其大小正确。 For example, if the default is 4-byte packing: 例如,如果默认为4字节打包:

struct X {
    char c;
    int i;
    double d;
};
extern const char g_check[sizeof(X)==16?1:-1];

Then #include this file at the start of every header (just write a program to put the extra includes in if there's too many to do by hand), and compile and see what happens. 然后,在每个标头的开头#include这个文件(只要编写一个程序,如果需要手工处理的话就把多余的包含在其中),然后编译并查看会发生什么。 This won't directly detect changes in struct layout, just non-standard packing settings, which is what you're interested in anyway. 这将不会直接检测结构布局的变化,而只会检测非标准包装设置,这正是您感兴趣的。

(When adding new headers one would put this #include at the top, along with the usual ifdef boilerplate and so on. This is unfortunate but I'm not sure there's any way around it. The best solution is probably to ask people to do it, but assume they'll forget, and run the extra-include-inserting program every now and again...) (添加新标题时,会将#include放在顶部,再加上通常的ifdef样板等等。这很不幸,但是我不确定是否有解决方法。最好的解决方案可能是要求人们去做它,但假设他们会忘记,并且不时地运行额外插入项目...)

Apologies for posting an answer - which this is not - but I don't know how to post code in comments. 很抱歉发布答案-这不是-但我不知道如何在注释中发布代码。 Sorry. 抱歉。

To wrap Brone's idea in a macro, here is what free we currently use (feel free to edit it): 要将Brone的想法包装在一个宏中,以下是我们目前使用的免费版本(可以自由编辑):

/** Our own assert macro, which will trace a FATAL error message if the assert
 * fails. A FATAL trace will cause a system restart.
 * Note: I would love to use CPPUNIT_ASSERT_MESSAGE here, for a nice clean
 * test failure if testing with CppUnit, but since this header file is used
 * by C code and the relevant CppUnit include file uses C++ specific
 * features, I cannot.
 */
#ifdef TESTING
/* ToDo: might want to trace a FATAL if integration testing */
#define ASSERT_MSG(subsystem, message, condition) if (!(condition)) {printf("Assert    failed: \"%s\" at line %d in file \"%s\"\n", message, __LINE__, __FILE__); fflush(stdout); abort();}

/* we can also use this, which prints of the failed condition as its message */
#define ASSERT_CONDITION(subsystem, condition) if (!(condition)) {printf("Assert failed: \%s\" at line %d in file \%s\"\n", #condition, __LINE__, __FILE__); fflush(stdout); abort();}
#else
#define ASSERT_MSG(subsystem, message, condition)  if (!condition) DebugTrace(FATAL, subsystem, __FILE__, __LINE__, "%s", message);
#define ASSERT_CONDITION(subsystem, condition)     if (!(condition)) DebugTrace(FATAL, subsystem, __FILE__, __LINE__, "%s", #condition);
#endif

What you would be looking for is an assertion like ASSERT_CONSISTENT(A_x, offsetof(A,x)) , placed in a header file. 您想要的是放置在头文件中的断言,如ASSERT_CONSISTENT(A_x, offsetof(A,x)) Let me explain why, and what the problem is. 让我解释原因以及问题所在。

Because the problem exists across translation units, you can only detect the error at link time. 由于翻译单元之间存在问题,因此只能在链接时检测到错误。 That means you need to force the linker to spit out an error. 这意味着您需要强制链接器吐出一个错误。 Unfortunately, most cross-translation unit problems are formally of the "no diagnosis needed" kind. 不幸的是,大多数跨翻译单元问题在形式上都是“无需诊断”的。 The most familiar one is the ODR rule. 最熟悉的一种是ODR规则。 We can trivially cause ODR violations with such assertions, but you just can't rely on the linker to warn you about them. 我们可以通过这样的断言轻微地导致违反ODR的行为,但您只是不能依靠链接程序来警告您有关它们的信息。 If you can, the implementation of the ODR can be as simple as 如果可以的话,ODR的实现可以很简单

#define ASSERT_CONSISTENT(label, x) class ASSERT_ ## label { char test[x]; };

But if the linker doesn't notice these ODR violations, this will pass by silently. 但是,如果链接程序没有注意到这些违反ODR的行为,则将以静默方式通过。 And here lies the problem: the linker really only needs to complain if it can't find something. 问题就出在这里:链接器实际上只需要在找不到内容时进行投诉即可。

With two macro's the problem is solved: 使用两个宏可以解决此问题:

template <int i> class dummy; // needed to differentiate functions
#define ASSERT_DEFINE(label, x) void ASSERT_label(dummy<x>&) { }
#define ASSERT_CHECK(label, x) void (*check)(dummy<x>&) = &ASSERT_label;

You'd need to put the ASSERT_DEFINE macro in a .cpp, and ASSERT_CHECK in its header. 您需要将ASSERT_DEFINE宏放在.cpp文件中,并在其标头中放入ASSERT_CHECK。 If the x value checked isn't the x value defined for that label, you're taking the address of an undefined function. 如果检查的x值不是为该标签定义的x值,则您正在使用未定义函数的地址。 Now, a linker doesn't need to warn about multiple definitions, but it must warn about missing definitions. 现在,链接器不需要警告多个定义,但是必须警告缺少的定义。

BTW, for this particular problem, see Diagnosing Hidden ODR Violations in Visual C++ (and fixing LNK2022) BTW,有关此特定问题,请参阅在Visual C ++中诊断隐藏的ODR违规(并修复LNK2022)

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

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