【问题标题】:How to use static assert in C to check the types of parameters passed to a macro如何在 C 中使用静态断言来检查传递给宏的参数类型
【发布时间】:2020-03-10 04:38:03
【问题描述】:

我需要编写一个 C 宏来检查以确保传递给它的所有参数都是unsigned 并且是相同的整数类型。例如:所有输入参数均为uint8_t,或全部为uint16_t,或全部为uint32_t,或全部为uint64_t

以下是在 C++ 中如何进行此类检查:Use static_assert to check types passed to macro

C 中是否存在类似的东西,即使只是通过 gcc 扩展?

请注意,静态断言可通过_Static_assert 在 gcc 中使用。 (在这里查看我的答案:Static assert in C)。

这不起作用:

int a = 1; 
int b = 2;
_Static_assert(__typeof__ a == __typeof__ b, "types don't match");

错误:

main.c: In function ‘main’:
main.c:23:20: error: expected expression before ‘__typeof__’
     _Static_assert(__typeof__ a == __typeof__ b, "types don't match");

更新:

这正是我想要在 C++ 中做的事情(使用 函数模板static_assert<type_traits> 头文件)。为了比较的目的,我无论如何都需要学习这个,所以我就这样做了。在此处为自己运行此代码:https://onlinegdb.com/r1k-L3HSL

#include <stdint.h>
#include <stdio.h>
#include <type_traits> // std::is_same()

// Templates: https://www.tutorialspoint.com/cplusplus/cpp_templates.htm

// Goal: test the inputs to a "C macro" (Templated function in this case in C++) to ensure
// they are 1) all the same type, and 2) an unsigned integer type

// 1. This template forces all input parameters to be of the *exact same type*, even 
//    though that type isn't fixed to one type! This is because all 4 inputs to test_func()
//    are of type `T`.
template <typename T>
void test_func(T a, T b, T c, T d)
{
    printf("test_func: a = %u; b = %u; c = %u; d = %u\n", a, b, c, d);

    // 2. The 2nd half of the check: 
    // check to see if the type being passed in is uint8_t OR uint16_t OR uint32_t OR uint64_t!
    static_assert(std::is_same<decltype(a), uint8_t>::value ||
                  std::is_same<decltype(a), uint16_t>::value ||
                  std::is_same<decltype(a), uint32_t>::value ||
                  std::is_same<decltype(a), uint64_t>::value,
                  "This code expects the type to be an unsigned integer type\n"
                  "only (uint8_t, uint16_t, uint32_t, or uint64_t).");

    // EVEN BETTER, DO THIS FOR THE static_assert INSTEAD!
    // IE: USE THE TEMPLATE TYPE `T` DIRECTLY!
    static_assert(std::is_same<T, uint8_t>::value ||
                  std::is_same<T, uint16_t>::value ||
                  std::is_same<T, uint32_t>::value ||
                  std::is_same<T, uint64_t>::value,
                  "This code expects the type to be an unsigned integer type\n"
                  "only (uint8_t, uint16_t, uint32_t, or uint64_t).");
}

int main()
{
    printf("Begin\n");

    // TEST A: This FAILS the static assert since they aren't unsigned 
    int i1 = 10;
    test_func(i1, i1, i1, i1); 

    // TEST B: This FAILS to find a valid function from the template since 
    // they aren't all the same type 
    uint8_t i2 = 11;
    uint8_t i3 = 12;
    uint32_t i4 = 13;
    uint32_t i5 = 14;
    test_func(i2, i3, i4, i5);

    // TEST C: this works!
    uint16_t i6 = 15;
    uint16_t i7 = 16;
    uint16_t i8 = 17;
    uint16_t i9 = 18;
    test_func(i6, i7, i8, i9);

    return 0;
}

如果 TEST A 未注释,您会在静态断言中遇到此故障,因为输入不是无符号的:

main.cpp: In instantiation of ‘void test_func(T, T, T, T) [with T = int]’:
<span class="error_line" onclick="ide.gotoLine('main.cpp',46)">main.cpp:46:29</span>:   required from here
main.cpp:32:5: error: static assertion failed: This code expects the type to be an unsigned integer type
only (uint8_t, uint16_t, uint32_t, or uint64_t).
     static_assert(std::is_same<decltype(a), uint8_t>::value ||
     ^~~~~~~~~~~~~

如果只取消注释 TEST B,您将无法从模板中找到有效函数,因为模板要求所有输入都是同一类型 T

main.cpp: In function ‘int main()’:
main.cpp:54:29: error: no matching function for call to ‘test_func(uint8_t&, uint8_t&, uint32_t&, uint32_t&)’
     test_func(i2, i3, i4, i5);
                             ^
main.cpp:26:6: note: candidate: template void test_func(T, T, T, T)
 void test_func(T a, T b, T c, T d)
      ^~~~~~~~~
main.cpp:26:6: note:   template argument deduction/substitution failed:
main.cpp:54:29: note:   deduced conflicting types for parameter ‘T’ (‘unsigned char’ and ‘unsigned int’)
     test_func(i2, i3, i4, i5);
                             ^

只要 TEST C 未注释,它就会通过,看起来像这样!

Begin
test_func: a = 15; b = 16; c = 17; d = 18

参考资料:

  1. http://www.cplusplus.com/reference/type_traits/is_same/
  2. https://en.cppreference.com/w/cpp/types/is_same
  3. https://en.cppreference.com/w/cpp/language/decltype
  4. How do I restrict a template class to certain built-in types?

相关:

  1. Use static_assert to check types passed to macro[我自己的回答]
  2. Static assert in C[我自己的回答]

【问题讨论】:

  • C 提供__typeof__(GNU C 提供typeof)允许您查询类型。见Referring to a Type with typeof
  • 是的,我想过这个问题,但没有办法使用它来比较类型——只能复制它们以进行实例化。例如:int a; __typeof__ a b; 生成 ab 类型为 int。但是,我上次检查时,_Static_assert(__typeof__ a == __typeof__ b, "this failed"); 似乎根本不起作用。
  • 我在想的是if (sizeof(__typeof__ a) == sizeof(__typeof__ b)) 会区分uintX_t 的口味。
  • 确实如此,但您甚至不需要__typeof__。只做:if (sizeof(a) == sizeof(b)),但这并不能帮助我区分signedunsigned(例如:int16_tuint16_t,它们都是2 个字节)、floatdoubleuint32_t 在 8 位机器上(在这种情况下这三种类型都是 4 字节),或 doubleuint64_t 在 64 位机器上(这两种类型都是 8 字节)等等。换句话说,sizeof 在我看来无效并且缺乏必要的粒度,除非有人知道我不知道的特殊使用方式。
  • 这是一个难题,因为您只能使用__typeof__,而typedef 将被允许——这限制了该值。我不知道还有什么能让你到达那里的——但我不会排除某个更聪明的人知道我在那个部门不知道的事情。它通常不是你在 C 中遇到的,因为它是强类型的。

标签: c gcc types macros typechecking


【解决方案1】:

如果这里最重要的方面是如果ab 是不同的类型,您希望它无法编译,您可以使用C11 的_Generic 以及GCC 的__typeof__ 扩展来管理它。

一个通用的例子:

#include <stdio.h>

#define TYPE_ASSERT(X,Y) _Generic ((Y), \
    __typeof__(X): _Generic ((X), \
        __typeof__(Y): (void)NULL \
    ) \
)

int main(void)
{
    int a = 1; 
    int b = 2;
    TYPE_ASSERT(a,b);
    printf("a = %d, b = %d\n", a, b);
}

现在如果我们尝试编译这段代码,它会编译得很好,每个人都很高兴。

如果我们把b的类型改成unsigned int,但是编译会失败。

这是因为_Generic 选择使用控制表达式的类型(在本例中为(Y))来选择要遵循的规则并插入与该规则对应的代码。在这种情况下,我们只为__typeof__(X) 提供了一个规则,因此如果(X) 不是(Y) 的兼容类型,则没有合适的规则可供选择,因此无法编译。为了处理具有将衰减为指针的控制表达式的数组,我添加了另一个_Generic,它以另一种方式确保它们必须相互兼容,而不是接受单向兼容。而且因为——就我特别关心的——我们只想确保它在不匹配时无法编译,而不是在匹配时执行特定的操作,所以我给相应的规则一个什么都不做的任务:(void)NULL

在一个极端情况下,这种技术会出错:_Generic 不处理可变可修改类型,因为它是在编译时处理的。因此,如果您尝试使用可变长度数组执行此操作,它将无法编译。

要处理固定宽度无符号类型的特定用例,我们可以修改嵌套的 _Generic 来处理它,而不是处理数组的特殊性:

#define TYPE_ASSERT(X,Y) _Generic ((Y), \
    __typeof__(X): _Generic ((Y), \
        uint8_t: (void)NULL, \
        uint16_t: (void)NULL, \
        uint32_t: (void)NULL, \
        uint64_t: (void)NULL \
   ) \
)

传递不兼容类型时的示例 GCC 错误:

main.c: In function 'main':
main.c:7:34: error: '_Generic' selector of type 'signed char' is not compatible with any association
    7 |         __typeof__(X): _Generic ((Y), \
      |                                  ^

值得一提的是,__typeof__ 作为 GCC 扩展,不会成为所有编译器都可移植的解决方案。不过,它似乎确实适用于 Clang,因此这是另一个支持它的主要编译器。

【讨论】:

  • 好答案。不幸的是,它遵循了我的劣质示例而不是我的描述。我有一个宏,其中预期无符号下溢和上溢,因此我需要验证所有输入是否为 uint8_t、uint16_t、uint32_t 或 uint64_t。它们必须是其中之一,而且都是一样的。不过,你给了我一个好的开始。此外,clang 声称“与 gcc 兼容”,因此他们强调尝试支持所有可能的 gcc 扩展和预处理器指令,并且语法也相同。然后 Clang 文档记录任何偏差或细微差别,并说要参考 gcc 文档。
  • @GabrielStaples 我已经更新了另一个更具体针对您的用例的示例。
【解决方案2】:

您想要的在标准 C11 中是可行的,不需要扩展或 GCC。

我们将建立最终答案,以便所有人都可以遵循。


根据 C11 标准 [6.7.10],static_assert-declaration: _Static_assert( constant-expression , string-literal ) 是一个声明。因此,如果我们要使用宏,我们最好为声明提供一个作用域,以保持整洁。通常是通常的形式:

#define MY_AMAZING_MACRO() do {_Static_assert(...some magic...);} while(0)

接下来,如果断言失败,我们的 _Static_assert 在宏中至少会通过 stdio 重复实际问题,请使用熟悉的字符串化设置:

#define STATIC_ASSERT_H(x)  _Static_assert(x, #x)
#define STATIC_ASSERT(x)    STATIC_ASSERT_H(x)

接下来,我们将使用 C11 的通用选择功能来声明一个宏,如果对象是我们要查找的类型,则返回常量 1,否则返回 0:

#define OBJ_IS_OF_TYPE(Type, Obj) _Generic(Obj, Type: 1, default: 0)

接下来我们将创建一个宏来测试您的所有四个输入是否都属于相同类型

#define ALL_OBJS_ARE_OF_TYPE(Type, Obj_0, Obj_1, Obj_2, Obj_3)  \
    (OBJ_IS_OF_TYPE(Type, Obj_0) &&                             \
     OBJ_IS_OF_TYPE(Type, Obj_1) &&                             \
     OBJ_IS_OF_TYPE(Type, Obj_2) &&                             \
     OBJ_IS_OF_TYPE(Type, Obj_3))

接下来,使用上面的方法,制作一个宏来测试你的所有四个输入是否都是进一步的四种类型之一

#define IS_ACCEPTABLE(Type_0, Type_1, Type_2, Type_3, Obj_0, Obj_1, Obj_2, Obj_3)   \
    (ALL_OBJS_ARE_OF_TYPE(Type_0, Obj_0, Obj_1, Obj_2, Obj_3) ||                    \
     ALL_OBJS_ARE_OF_TYPE(Type_1, Obj_0, Obj_1, Obj_2, Obj_3) ||                    \
     ALL_OBJS_ARE_OF_TYPE(Type_2, Obj_0, Obj_1, Obj_2, Obj_3) ||                    \
     ALL_OBJS_ARE_OF_TYPE(Type_3, Obj_0, Obj_1, Obj_2, Obj_3))

最后,把它们放在一起:

#define TEST_FUNC(a,b,c,d)                                              \
do                                                                      \
{                                                                       \
    STATIC_ASSERT(IS_ACCEPTABLE(uint8_t, uint16_t, uint32_t, uint64_t,  \
                                a,       b,        c,        d));       \
} while(0)

当然,如果您希望在任何 _Static_asserts 失败时输出更详细的错误输出,您可以根据需要将上述内容分成更不同的单独 STATIC_ASSERT。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-04-30
    • 1970-01-01
    • 2021-02-13
    • 2011-06-10
    • 2011-02-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多