几个用到不多却很有用的C语言特性和小Tricky
2016年09月17日 C

几个C语言的特性和tricky,平时不常用到,却很有用,这里记录一下。

  1. 数组传参时的static修饰,编译期检查数据长度,类似void test_static(char param[static 5]),意思就是传入的param的char类型数组参数至少要有五个元素。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // char param[static 1]检测NULL
    void test_static(char param[static 5])
    {
    // code here
    }

    int main()
    {
    char x[3] = {1, 2};
    test_static(x);
    return 0;
    }

    上述写法就会出现如下警告:

    warning: array argument is too small; contains 3 elements, callee requires at least 5 [-Warray-bounds]

    可参考这里

  2. 动态指定printf输出数据的长度。可以用这种方式指定浮点数小数部分的保留位数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    int main(int argc, char *argv[])
    {
    int pre = 3;
    float num = 1.2432;
    char *str = "test statements";
    printf("num = %.*f\n", pre, num); // 输出 1.234
    printf("str = %.*s\n", pre, str); // 输出 tes
    return 0;
    }
  3. printf还有一个格式参数%n,这个参数不输出任何内容,但是会把%n所在位置之前的字符计数都放入一个整型数值中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    int main(int argc, char *argv[])
    {
    int pre = 4;
    int out_num = 0;
    char *str = "test statements";
    // 回车之前的字符计数都会放在out_num中
    int ret = printf("str = %.*s%n\n", pre, str, &out_num);
    printf("out_num=%d, ret = %d\n", out_num, ret);
    return 0;
    }

    输出:

    str = test
    out_num=10, ret = 11

  4. c11添加了_Static_assert静态断言,在c11之前可以自行定义一个『静态断言』,即条件为假制造编译错误。(from stackoverflow)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    #define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(!!(COND))*2-1]
    #define COMPILE_TIME_ASSERT3(X,L) STATIC_ASSERT(X,static_assertion_at_line_##L)
    #define COMPILE_TIME_ASSERT2(X,L) COMPILE_TIME_ASSERT3(X,L)
    #define COMPILE_TIME_ASSERT(X) COMPILE_TIME_ASSERT2(X,__LINE__)

    int main()
    {
    COMPILE_TIME_ASSERT(sizeof(int)==5);
    }

    编译提示如下:

    error: ‘static_assertion_static_assertion_at_line_11’ declared as an array with a negative size

    COMPILE_TIME_ASSERT(sizeof(int) == 3);

  5. 编译器的尝试性定义,代码:

    1
    2
    3
    4
    5
    6
    7
    8
    int i;
    int i = 10; // 或者int i;

    int main(int argc, char *argv[])
    {
    printf("%d\n", i);
    return 0;
    }

    这段代码不会报错,C标准是这样来解释的,因为声明是出现在文件域的,不是出现在某个函数域内,把其当做外部定义,同时编译器还根据定义是否有初始值来区分,带初值的就叫做定义,没有初始值的也没extern关键的字就是尝试性的定义,如果文件中同时出现了这二种(也就是一个是定义,一个是尝试性定义,像上文代码),这样那个没有带初始值的就直接当做冗余定义来处理了,不会当做redefinition,如果二个都是没带初值的,也没带extern关键字的,编译器就会把这二个尝试性定义合并成为一个初始值为0的非尝试性定义。现在上面的代码会输出10,如果把int i = 10改为int i;,代码会输出0。

  6. 不能在一个返回类型为void的函数中写return void,但是可以返回执行一个返回void的函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    void foo(int x)
    {

    }

    void bar()
    {
    return foo(10);
    }

    int main(int argc, char *argv[])
    {
    bar();
    return 0;
    }
  7. 网上很多例子说三元运算符可以这样用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include <stdio.h>

    int main(int argc, char *argv[])
    {
    int x = 10;
    int y = 0;
    (y < 0 ? x : y) = 20;
    printf("%d %d\n", x, y);
    return 0;
    }

    也就是说三元运算符会产生一个左值,c语言里面是不可以的,这样编译:

    gcc -Wall -std=c99 test.c -o test

    会报编译错误,提示你表达式不能被赋值(error: expression is not assignable)。

    但是在c++里的确可以。也就是上面这段代码用g++编译(会有警告)或者直接写一段c++的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include <cstdio>

    int main(int argc, char *argv[])
    {
    int x = 10;
    int y = 0;
    (y < 0 ? x : y) = 20;
    printf("%d %d\n", x, y);
    return 0;
    }

    g++ -Wall test.cpp -o test

    ./test

    会输出10 20。