toolchain教學 C編譯器斷言-如何實現?




toolchain教學 (8)

正如Leander所說,靜態斷言正在被添加到C ++ 11中,現在他們已經有了。

static_assert(exp, message)

例如

#include "myfile.hpp"

static_assert(sizeof(MyClass) == 16, "MyClass is not 16 bytes!")

void doStuff(MyClass object) { }

請參閱上面的cppreference頁面

我想在錯誤情況下實現一個“斷言”來阻止編譯,而不是在運行時失敗。

我目前有一個像這樣的定義,它工作得很好,但它增加了二進製文件的大小。

#define MY_COMPILER_ASSERT(EXPRESSION) switch (0) {case 0: case (EXPRESSION):;}

示例代碼(無法編譯)。

#define DEFINE_A 1
#define DEFINE_B 1
MY_COMPILER_ASSERT(DEFINE_A == DEFINE_B);

我如何實現它,以便它不生成任何代碼(為了最小化生成的二進製文件的大小)?


我知道你對C感興趣,但看看boost的C ++ static_assert 。 (順便說一句,這很可能在C ++ 1x中可用。)

我們為C ++做了類似的事情:

#define COMPILER_ASSERT(expr)  enum { ARG_JOIN(CompilerAssertAtLine, __LINE__) = sizeof( char[(expr) ? +1 : -1] ) }

這顯然只適用於C ++。 pixelbeat討論了一種修改它以便在C中使用的方法。


我發現這給GCC提供了最簡單的錯誤消息。 其他所有東西都有一些關於負面大小或其他令人困惑的東西的後綴:

#define STATIC_ASSERT(expr, msg)   \
typedef char ______Assertion_Failed_____##msg[1];  __unused \
typedef char ______Assertion_Failed_____##msg[(expr)?1:2] __unused

示例用法:

 unsigned char testvar;
 STATIC_ASSERT(sizeof(testvar) >= 8, testvar_is_too_small);

和gcc中的錯誤消息(ARM / GNU C編譯器:6.3.1):

conflicting types for '______Assertion_Failed_____testvar_is_too_small'

純標準C中的編譯時斷言是可能的,並且一些預處理器技巧使其使用看起來與assert()的運行時使用一樣乾淨。

關鍵技巧是找到一個可以在編譯時進行評估的構造,並且可能導致某些值出錯。 一個答案是數組的聲明不能具有負大小。 使用typedef可防止成功時分配空間,並在失敗時保留錯誤。

錯誤消息本身將隱式引用負大小的聲明(GCC稱“數組foo的大小為負”),因此您應該為數組類型選擇一個名稱,該名稱暗示此錯誤確實是斷言檢查。

要處理的另一個問題是,只能在任何編譯單元中鍵入一次特定類型名稱。 因此,宏必須安排每個用法以獲取要聲明的唯一類型名稱。

我通常的解決方案是要求宏有兩個參數。 第一個是assert的條件為true,第二個是在幕後聲明的類型名稱的一部分。 plinth的答案提示使用令牌粘貼和__LINE__預定義宏來形成一個唯一的名稱,可能不需要額外的參數。

不幸的是,如果斷言檢查在包含的文件中,它仍然可能與第二個包含文件中相同行號的檢查衝突,或者與主源文件中的該行號衝突。 我們可以通過使用宏__FILE__來解決這個問題,但它被定義為一個字符串常量,並且沒有預處理器技巧可以將字符串常量轉換回標識符名稱的一部分; 更不用說合法文件名可以包含不是標識符合法部分的字符。

所以,我建議使用以下代碼片段:

/** A compile time assertion check.
 *
 *  Validate at compile time that the predicate is true without
 *  generating code. This can be used at any point in a source file
 *  where typedef is legal.
 *
 *  On success, compilation proceeds normally.
 *
 *  On failure, attempts to typedef an array type of negative size. The
 *  offending line will look like
 *      typedef assertion_failed_file_h_42[-1]
 *  where file is the content of the second parameter which should
 *  typically be related in some obvious way to the containing file
 *  name, 42 is the line number in the file on which the assertion
 *  appears, and -1 is the result of a calculation based on the
 *  predicate failing.
 *
 *  \param predicate The predicate to test. It must evaluate to
 *  something that can be coerced to a normal C boolean.
 *
 *  \param file A sequence of legal identifier characters that should
 *  uniquely identify the source file in which this condition appears.
 */
#define CASSERT(predicate, file) _impl_CASSERT_LINE(predicate,__LINE__,file)

#define _impl_PASTE(a,b) a##b
#define _impl_CASSERT_LINE(predicate, line, file) \
    typedef char _impl_PASTE(assertion_failed_##file##_,line)[2*!!(predicate)-1];

典型用法可能是這樣的:

#include "CAssert.h"
...
struct foo { 
    ...  /* 76 bytes of members */
};
CASSERT(sizeof(struct foo) == 76, demo_c);

在GCC中,斷言失敗看起來像:

$ gcc -c demo.c
demo.c:32: error: size of array `assertion_failed_demo_c_32' is negative
$

我在C中的靜態斷言中找到的最佳寫法是pixelbeat 。 請注意,靜態斷言正被添加到C ++ 0X中,並且可能會進入C1X,但這不會有一段時間。 我不知道我給出的鏈接中的宏是否會增加二進製文件的大小。 我懷疑他們不會,至少如果你在合理的優化水平上編譯,但你的里程可能會有所不同。


以下COMPILER_VERIFY(exp)宏工作得很好。

// combine arguments (after expanding arguments)
#define GLUE(a,b) __GLUE(a,b)
#define __GLUE(a,b) a ## b

#define CVERIFY(expr, msg) typedef char GLUE (compiler_verify_, msg) [(expr) ? (+1) : (-1)]

#define COMPILER_VERIFY(exp) CVERIFY (exp, __LINE__)

它適用於C和C ++,可以在任何允許使用typedef的地方使用。 如果表達式為true,則為1 char的數組生成typedef(這是無害的)。 如果表達式為false,則為-1個字符的數組生成typedef,這通常會導致錯誤消息。 作為arugment給出的表達式可以是任何求值為編譯時常量的表達式(因此涉及sizeof()的表達式可以正常工作)。 這使它比它更靈活

#if (expr)
#error
#endif

您可以將其限制為可由預處理器評估的表達式。


好吧,你可以static_assert使用static_assert

我相信他們在那裡做的是定義一個數組。

 #define MY_COMPILER_ASSERT(EXPRESSION) char x[(EXPRESSION)];

如果EXPRESSION為true,則定義char x[1]; ,這沒關係。 如果為false,則定義char x[0]; 這是非法的。


使用'#error'是一個有效的預處理器定義,導致編譯在大多數編譯器上停止。 你可以這樣做,例如,為了防止在調試中編譯:


#ifdef DEBUG
#error Please don't compile now
#endif




assertions