_Generic关键字及其语法和应用(C11标准),C语言_Generic详解

对接触过面向对象程序设计的程序员来讲,相信各位对泛型编程并不陌生。在 C11 标准中,_Generic 关键字可以让 C 语言也如同 C++ 等面向对象程序设计语言一样,使其支持轻量级的泛型编程设计。

利用 _Generic 关键字,可以简单地将一组具有不同类型却有相同功能的函数抽象为一个统一的接口,语法形式如下:

generic-selection:
    _Generic (assignment-expression, generic-assoc-list)
generic-assoc-list:
    generic-association
    generic-assoc-list , generic-association
generic-association:
    type-name : assignment-expression
    default : assignment-expression

与 sizeof 与 typeof 类似,_Generic 中的 assignment-expression 只用于在编译时获得该表达式的类型,而不会对该表达式做运行时计算,如以下代码所示。
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <stdint.h>
#define getTypeName(x) _Generic((x), _Bool:"_Bool",\
    char: "char", \
    signed char: "signed char", \
    unsigned char: "unsigned char", \
    short int: "short int", \
    unsigned short int: "unsigned short int", \
    int: "int", \
    unsigned int: "unsigned int", \
    long int: "long int", \
    unsigned long int: "unsigned long int", \
    long long int: "long long int", \
    unsigned long long int: "unsigned long long int", \
    float: "float", \
    double: "double", \
    long double: "long double", \
    char *: "pointer to char", \
    void *: "pointer to void", \
    int *: "pointer to int")
int main(void)
{
    char c = 'a';
    size_t s;
    ptrdiff_t p;
    intmax_t i;
    int arr[3] = { 0 };
    printf("s is '%s'\n", getTypeName(s));
    printf("p is '%s'\n", getTypeName(p));
    printf("i is '%s'\n", getTypeName(i));
    printf("c is '%s'\n", getTypeName(c));
    printf("arr is '%s'\n", getTypeName(arr));
    printf("0x7FFFFFFF is '%s'\n", getTypeName(0x7FFFFFFF));
    printf("0xFFFFFFFF is '%s'\n", getTypeName(0xFFFFFFFF));
    printf("0x7FFFFFFFU is '%s'\n", getTypeName(0x7FFFFFFFU));
}
运行结果为:
s is 'unsigned int'
p is 'int'
i is 'long long int'
c is 'char'
arr is 'pointer to int'
0x7FFFFFFF is 'int'
0xFFFFFFFF is 'unsigned int'
0x7FFFFFFFU is 'unsigned int'

除此之外,还必须保证 generic-association-list 中有与 assignment-expression 类型相同的 generic-association 与之对应,否则编译就会报错。例如,在上面代码的main函数中添加如下两行代码:
float *fp=NULL;
printf("fp is '%s'\n", getTypeName(fp));
很显然,generic-assoc-list 中没有 generic-association 与 fp 相匹配的类型,从而导致编译出错,如图 1 所示。


图 1 类型不匹配

要解决图 1 这种类型不匹配时导致的编译错误,你可以在 generic-association-list 中添加 default 处理,那么编译就能够顺利进行,如下面的代码所示:
#define getTypeName(x) _Generic((x), _Bool:"_Bool",\
    char: "char", \
    signed char: "signed char", \
    unsigned char: "unsigned char", \
    short int: "short int", \
    unsigned short int: "unsigned short int", \
    int: "int", \
    unsigned int: "unsigned int", \
    long int: "long int", \
    unsigned long int: "unsigned long int", \
    long long int: "long long int", \
    unsigned long long int: "unsigned long long int", \
    float: "float", \
    double: "double", \
    long double: "long double", \
    char *: "pointer to char", \
    void *: "pointer to void", \
    int *: "pointer to int",\
    default: "other")
现在,如果编译器发现 generic-assoc-list 中没有 generic-association 与 fp 相匹配的类型时,将默认执行 default 处理,运行结果为:

s is 'unsigned int'
p is 'int'
i is 'long long int'
c is 'char'
arr is 'pointer to int'
fp is 'other'
0x7FFFFFFF is 'int'
0xFFFFFFFF is 'unsigned int'
0x7FFFFFFFU is 'unsigned int'