最近我在一个相对较大的项目中发现,由于各种标题在不同的cpp文件中以不同的顺序包含在内,因此发生了丑陋的运行时崩溃。
这些标题包括#pragma pack - 这些pragma有时不是'关闭'(我的意思是,回到编译器默认的#pragma pack()) - 导致不同目标文件中的不同对象布局。难怪当应用程序访问在一个模块中创建并传递给另一个模块的struct成员时,应用程序崩溃了。或者派生类从基类访问成员。
由于我喜欢从我发现的每个bug中创建一个更通用的调试和断言策略的想法,我真的想断言对象布局总是和所有地方都一样。
因此断言
会很容易ASSERT(offsetof(membervar)== 4)
但是这不会在另一个模块中捕获不同的布局 - 或者在结构布局发生变化时需要手动更新..所以我最喜欢的想法是
ASSERT(offsetof(membervar)== offsetof(othermodule_membervar))
这可以用断言吗?或者这是单元测试的情况吗?
谢谢, ħ
答案 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 ++,则可能会对以下内容感兴趣:
可能有一定的余地可以创建一种半自动化方式来描述流程,整理输出和交叉引用。
为了更自动地检测这类问题,我会发生以下情况。创建一个文件,该文件定义一个结构,该结构将具有指定的默认包装数量的特定大小,但具有不同包装值的不同大小。还包括一些静态断言,它的大小是正确的。例如,如果默认值为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)