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

时间:2010-02-13 23:31:29

标签: c++

最近我在一个相对较大的项目中发现,由于各种标题在不同的cpp文件中以不同的顺序包含在内,因此发生了丑陋的运行时崩溃。

这些标题包括#pragma pack - 这些pragma有时不是'关闭'(我的意思是,回到编译器默认的#pragma pack()) - 导致不同目标文件中的不同对象布局。难怪当应用程序访问在一个模块中创建并传递给另一个模块的struct成员时,应用程序崩溃了。或者派生类从基类访问成员。

由于我喜欢从我发现的每个bug中创建一个更通用的调试和断言策略的想法,我真的想断言对象布局总是和所有地方都一样。

因此断言

会很容易

ASSERT(offsetof(membervar)== 4)

但是这不会在另一个模块中捕获不同的布局 - 或者在结构布局发生变化时需要手动更新..所以我最喜欢的想法是

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

这可以用断言吗?或者这是单元测试的情况吗?

谢谢, ħ

6 个答案:

答案 0 :(得分:2)

  
    

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

  

我看不出让技术上成为可能的方法。此外,即使它在物理上是可能的,也是不实际的。你需要为每对源文件设置一个断言:

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) )

答案 1 :(得分:1)

您可以通过在不同文件中声明sizeof(类)来逃避这种情况。如果打包导致对象的大小变小,那么我认为sizeof()会显示出来。

你也可以使用C ++ 0x的静态断言或Boost(或者当然是手动的)来做静态断言

如果不希望在每个文件中执行此操作,我建议将包含您担心的所有标头的头文件和static_asserts放在一起。

就个人而言,我只是建议在pragma列表中搜索代码库并修复它们。

答案 2 :(得分:1)

温迪,

在Win32中,有一些函数可以填充给定结构的不同版本。多年来,FOOBAR结构可能添加了新功能,因此它们创建了FOOBAR2或FOOBAREX。在某些情况下,有两个以上的版本。

无论如何,他们处理这个的方式是让调用者除了指向结构的指针之外还传入sizeof(theStruct)

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

SomWin32Api()的实现中,他们检查第一个参数并确定他们正在处理的结构版本。

您可以在调试版本中执行类似操作,以确保调用者和被调用者同意所引用的结构的大小,并断言该值是否与预期大小不匹配。使用宏,您甚至可以自动/隐藏它,以便它只在调试版本中发生。

不幸的是,这是运行时检查而不是编译时检查......

答案 3 :(得分:1)

你想要的并不是直接可能的。如果您使用的是VC ++,则可能会对以下内容感兴趣:

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

可能有一定的余地可以创建一种半自动化方式来描述流程,整理输出和交叉引用。

为了更自动地检测这类问题,我会发生以下情况。创建一个文件,该文件定义一个结构,该结构将具有指定的默认包装数量的特定大小,但具有不同包装值的不同大小。还包括一些静态断言,它的大小是正确的。例如,如果默认值为4字节打包:

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

然后#include这个文件位于每个标题的开头(只需编写一个程序,如果有太多的东西可以手工填写额外的包含),并编译并查看会发生什么。这不会直接检测结构布局的变化,只是非标准的包装设置,这是你感兴趣的。

(当添加新标题时,会将此#include放在顶部,以及通常的ifdef样板文件等等。这很不幸但我不确定是否有任何解决方法。最好的解决方案可能是要问人们去做,但假设他们会忘记,并且不时地运行额外包含插入程序......)

答案 4 :(得分:0)

发表答案的道歉 - 这不是 - 但我不知道如何在评论中发布代码。遗憾。

要将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

答案 5 :(得分:0)

你要找的是像ASSERT_CONSISTENT(A_x, offsetof(A,x))这样的断言,放在头文件中。让我解释一下原因,以及问题所在。

由于翻译单元存在问题,因此您只能在链接时检测错误。这意味着您需要强制链接器吐出错误。不幸的是,大多数交叉翻译单元问题都是正式的“无需诊断”类型。最熟悉的是ODR规则。我们可以通过这样的断言轻易地导致ODR违规,但您不能依赖链接器来警告它们。如果可以,ODR的实现可以像

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

但是如果链接器没有注意到这些ODR违规,那么它将以静默方式传递。这就是问题所在:链接器真的只需要抱怨它是否找不到东西。

有两个宏,问题就解决了:

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;

您需要将ASSERT_DEFINE宏放在.cpp中,并在其标题中放置ASSERT_CHECK。如果选中的x值不是为该标签定义的x值,则表示您正在使用未定义函数的地址。现在,链接器不需要警告多个定义,但必须警告缺少定义。

顺便说一句,对于这个特殊问题,请参阅Diagnosing Hidden ODR Violations in Visual C++ (and fixing LNK2022)