2010-02-13 3 views
2

최근에 비교적 큰 프로젝트에서 여러 헤더가 서로 다른 순서로 다른 cpp 파일에 포함되어 있기 때문에 추한 런타임 충돌이 발생했습니다.모듈 간 컴파일 시간 * assertions */c,

이 헤더에는 #pragma pack이 포함되어 있습니다.이 pragma는 때로는 '닫혀 있지 않습니다'(컴파일러의 기본 #pragma pack()으로 다시 설정 됨)되어 다른 객체 파일의 객체 레이아웃이 달라집니다. 하나의 모듈에서 생성되고 다른 모듈로 전달되는 구조체 멤버에 액세스 할 때 응용 프로그램이 충돌하는 것은 놀라운 일이 아닙니다. 또는 기본 클래스에서 멤버에 액세스하는 파생 클래스입니다.

나는 모든 버그로부터 좀 더 일반적인 디버깅 및 단정 전략을 만드는 아이디어를 좋아하기 때문에 항상 객체 레이아웃이 항상 동일하고 어디 에나 동일하다는 것을 주장하고 싶습니다.

그래서

ASSERT (을 offsetof (membervar) == 4)

을 주장하기 쉬울 것이다 그러나 이것은 또 다른 모듈에서 다른 레이아웃을 잡을 것 - 또는 수동 업데이트를 할 때마다 구조체 레이아웃 변경 필요 .. 그래서 내 마음에 드는 생각은 (membervar (을 offsetof) ==을 offsetof (othermodule_membervar))

ASSERT 같은 것

이 주장과 함께 할 수 있을까요? 아니면 단위 테스트의 경우입니까?

감사합니다, H

답변

1

당신은 다른 파일에서를 sizeof (클래스)을 주장하여 멀리 이와받을 수 있습니다. 패킹이 객체의 크기를 작게 만드는 경우, sizeof()가이를 나타낼 것으로 예상합니다.

또한, 내가하고자 모든 파일에서이 작업을 수행하고자하지 않는 부분에 정적 C++ 0X의 정적 어설 사용 어설, 또는 부스트 (또는 물론 handrolled 일)

으로이 작업을 수행 할 수

걱정되는 모든 헤더를 포함하는 헤더 파일과 static_asserts를 함께 사용하는 것이 좋습니다.

개인적으로는 코드베이스를 통해 pragma 목록을 검색하고 수정하는 것이 좋습니다.

+2

참조 pragma push/pop. –

1

웬디는 Win32에서

는, 주어진 구조체의 다른 버전을 채울 수 있습니다 하나의 기능이 있습니다. 수년 동안 FOOBAR 구조체에 새로운 기능이 추가되어 FOOBAR2 또는 FOOBAREX가 만들어졌습니다. 경우에 따라 두 개 이상의 버전이 있습니다.

어쨌든, 그들은 처리하는 방식이 호출자가 구조체의 포인터 외에 sizeof(theStruct)를 전달하는 것입니다 : SomWin32Api()의 구현 내에서

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

, 그들은 첫 번째 매개 변수를 확인하고 버전을 확인 그들이 다루고있는 구조체의

디버그 빌드에서 호출자와 호출 수신자가 참조되는 구조체의 크기에 동의하는지 확인하고 값이 예상되는 크기와 일치하지 않는지 주장 할 수 있습니다. 매크로를 사용하면 디버그 빌드에서만 자동으로 매크로를 숨기거나 숨길 수 있습니다.

불행하게도,이 런타임 검사가 아닌 컴파일 시간 체크 ...

2

ASSERT입니다 (을 offsetof는 (membervar) ==을 offsetof() othermodule_membervar)

기술적으로 가능한 방법을 찾을 수 없습니다. 또한, phyiscally 가능했다하더라도, 그것은 실용적이지 않습니다. 당신은 소스 파일의 모든 쌍에 대한 어설 필요 했어 : 당신이 원하는 것은 같은 직접 수 없습니다

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

+0

동등성은 전 이적입니다. a == b 및 b == c 인 경우 a == c. O (N * N) 문제를 O (N)로 줄입니다. – MSalters

+0

실제로 O (합계 N-1)입니다.이 예에서는 제가 보여준 것 같습니다. –

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 상용구와 함께 상단에 넣는 등 불행합니다. 그러나 그 주위에는 어떤 방법이 있는지 확실하지 않습니다. 사람들에게 그것을하도록 권유하지만 그들이 잊어 버리겠다고 추측하고 매시간 과외 삽입 프로그램을 실행하십시오 ...)

0

사과를 게시하는 것에 사과드립니다. -하지만 그렇지 않습니다. 코멘트에 코드를 게시하는 방법을 알아야합니다. 죄송합니다.

/** 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 
0

뭘 찾는 것입니다 것은 헤더에 배치 ASSERT_CONSISTENT(A_x, offsetof(A,x)) 같은 주장이다 :

매크로에 Brone의 아이디어를 포장하기 위해, 우리가 현재 사용하고 (편집 할 주시기 바랍니다) 무료 것입니다 파일. 왜, 그리고 무엇이 문제인지 설명해 드리겠습니다.

번역 단위에 문제가 있기 때문에 링크 할 때만 오류를 감지 할 수 있습니다. 즉 링커가 오류를 내야 할 필요가 있음을 의미합니다. 불행히도, 대부분의 교차 번역 단위 문제는 공식적으로 "진단 필요 없음"종류입니다. 가장 익숙한 것은 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 매크로를 넣을 필요가 있고, 헤더에 ASSERT_CHECK 것입니다. x 값이 해당 레이블에 정의 된 x 값이 아닌 경우 정의되지 않은 함수의 주소를 사용합니다. 이제 링커는 여러 정의에 대해 경고 할 필요가 없지만 에 대해 경고해야합니다. 은 누락되었습니다. 정의가 없습니다. 당신이 프라그 마를를 해결할 수없는 경우

BTW,이 특정 문제에 대한, 어쩌면 당신은에 그 헤더의`#의 include` 포장 수, (그들은 제 3 자 라이브러리에있어) Diagnosing Hidden ODR Violations in Visual C++ (and fixing LNK2022)