奇怪的C语言写法

C语言是一门博大精深的语言,有些高手灵活应用写出的代码使得我们有些菜鸟很难看得明白,这是对一些C语言的奇怪写法进行收录介绍:


一、do{}while(0)

linux内核和其他一些开源的代码中,经常会遇到这样的代码:


do{
 ...
}while(0)
这样的代码一看就不是一个循环,do..while表面上在这里一点意义都没有,那么为什么要这么用呢?


实际上,do{...}while(0)的作用远大于美化你的代码。查了些资料,总结起来这样写主要有以下几点好处:


1、辅助定义复杂的宏,避免引用的时候出错:


举例来说,假设你需要定义这样一个宏:


#define DOSOMETHING()\
               foo1();\
               foo2();
这个宏的本意是,当调用DOSOMETHING()时,函数foo1()和foo2()都会被调用。但是如果你在调用的时候这么写:


if(a>0)
    DOSOMETHING();
因为宏在预处理的时候会直接被展开,你实际上写的代码是这个样子的:


if(a>0)
    foo1();
foo2();
这就出现了问题,因为无论a是否大于0,foo2()都会被执行,导致程序出错。


那么仅仅使用{}将foo1()和foo2()包起来行么?


我们在写代码的时候都习惯在语句右面加上分号,如果在宏中使用{},代码里就相当于这样写了:“{...};”,展开后就是这个样子:


if(a>0)
{
    foo1();
    foo2();
};
这样甚至不会编译通过。所以,很多人才采用了do{...}while(0);


#define DOSOMETHING() \
        do{ \
          foo1();\
          foo2();\
        }while(0)\
    
...
 
if(a>0)
    DOSOMETHING();
 
...
这样,宏被展开后,才会保留初始的语义。GCC提供了Statement-Expressions用以替代do{...}while(0); 所以你也可以这样定义宏:


#define DOSOMETHING() ({\
        foo1(); \
        foo2(); \
})
http://www.spongeliu.com/ 
2、避免使用goto对程序流进行统一的控制:


有些函数中,在函数return之前我们经常会进行一些收尾的工作,比如free掉一块函数开始malloc的内存,goto一直都是一个比较简便的方法:


int foo()
{
    somestruct* ptr = malloc(...);
 
    dosomething...;
    if(error)
    {
        goto END;
    }
 
    dosomething...;
    if(error)
    {
        goto END;
    }
    dosomething...;
 
END:
    free(ptr);
    return 0;
 
}
由于goto不符合软件工程的结构化,而且有可能使得代码难懂,所以很多人都不倡导使用,那这个时候就可以用do{}while(0)来进行统一的管理:


int foo()
{
 
    somestruct* ptr = malloc(...);
 
    do{
        dosomething...;
        if(error)
        {
            break;
        }
 
        dosomething...;
        if(error)
        {
            break;
        }
        dosomething...;
    }while(0);
 
    free(ptr);
    return 0;
 
}
这里将函数主体使用do()while(0)包含起来,使用break来代替goto,后续的处理工作在while之后,就能够达到同样的效果。


 


3、避免空宏引起的warning


内核中由于不同架构的限制,很多时候会用到空宏,在编译的时候,空宏会给出warning,为了避免这样的warning,就可以使用do{}while(0)来定义空宏:


#define EMPTYMICRO do{}while(0)
 


4、定义一个单独的函数块来实现复杂的操作:


当你的功能很复杂,变量很多你又不愿意增加一个函数的时候,使用do{}while(0);,将你的代码写在里面,里面可以定义变量而不用考虑变量名会同函数之前或者之后的重复。


二、(void)(&__x == &__y);

在GCC的文档中建议使用如下的min宏定义:
#define min(X,Y)  \
(__extension__  \
({  \
   typeof(X) __x=(X), __y=(Y);   \

 (void)(&__x == &__y);\
   (__x<__y)?__x:__y;  \
}) \
)


本文讨论了这样作法的意义。


1、传统的min带来的副作用
2、GCC中的({statement list})的扩展
3、typeof(expression)
4、__extension__的含义
5、使用typeof和({})实现min,避免了副作用
附录1、旧版本的的GCC中的的解决方法
附录2、C++中使用template的解决方法


1、传统的min带来的副作用
min通常被定义成这样的宏:
#define min(X,Y) ((X) < (Y) ? (X) : (Y))
复制代码


这种定义会带来一些副作用,看下面的例子:
int x = 1, y = 2;
int main()
{
   printf("min=%d\n", min(x++, y++));
   printf("x = %d, y = %d\n", x, y);
}
复制代码


执行完min(x++、y++),我们期望x的值为2,y的值为3。


但是,实际的结果是,执行完mini(x++、y++)后,x的值为3,y的值为3,原因在于宏展开后x++被执行了两次:
int x = 1, y = 2;;
int main()
{
   printf("min=%d\n", x++ < y++ ? x++ : y++);
   printf("x = %d, y = %d\n", x, y);
}




2、GCC中的({statement list})的扩展
({statement list})是一个表达式,逗号表达式类似,但是功能更强,({与})中可以包含有多条语句(可以是变量定义、复杂的控制语句),该表达式的值为statement list中最后一条语句的值,举例:
int main()
{
    int result = ({
     int i, sum = 0;
     for (i = 1; i <= 100; i++)
          sum+= i;
     sum;
    })
    printf("result=%d\n", result);
}
运行结果:
result=5050
复制代码




3、typeof(expression)
typeof(expression)用来获取expression的类型,举例:
int main()
{
   int integer;
   typeof(100) i;  /* 表达式100的类型是int,定义了int型变量i */
   typeof(integer) j; /* 表达式integer的类型是int,定义了int型变量j */
   
   i = 1;
   j = 2;
}
复制代码




4、__extension__的含义
GCC引入了很多标准C中的没有的扩展,如({和)},GCC提供了pednatic选项用于检测程序是否使用了GCC的扩展,当使用pedantic选项编译如下程序时
int main()
{
    int result = ({
     int i, sum = 0;
     for (i = 1; i <= 100; i++)
          sum+= i;
     sum;
    })
    printf("result=%d\n", result);
}
复制代码


编译器发出警告:
$ cc -pedantic test.c
test.c: 在函数 ‘main’ 中:
test.c:9: 警告:ISO C 不允许在表达式中使用花括号组
复制代码


编译器提醒程序员,这段C程序使用了不符合ISO C标准的语法,如果使用其他的编译器(非GCC)编译这段代码有能会出错。在所有使用GNU 扩展关键字的表达式之前加__extension__ 关键字后,使用pedantic选项编译时,编译器就不再发出警告信息:
int main()
{
    int result = __extension__({
    int i, sum = 0;
     for (i = 1; i <= 100; i++)
          sum+= i;
     sum;
    })
    printf("result=%d\n", result);
}
$ cc -pedantic test.c
$ 编译成功!
复制代码




5、使用typeof和({})实现min,避免了副作用
#define min(X,Y) \
({  \
typeof(X) __x=(X), __y=(Y);  \
(__x<__y)?__x:__y;  \
})
复制代码


使用传统的min会出现问题的例子:
int x = 1, y = 2;;
int main()
{
   printf("min=%d\n", min(x++, y++));
   printf("x = %d, y = %d\n", x, y);
}
复制代码


它被扩展为
int x = 1, y = 2;;
int main()
{
   printf("min=%d\n", 
   ({
       typeof(x) __x = (x++), __y = (y++);  /* 定义了两个整形变量 */

   (void)(&__x == &__y);/* 这样当类型不兼容时会有警告。尤其有符号和无符号的比较,这很重要。*/
       (__x<__y)?__x:__y; 
   })
   );
   printf("x = %d, y = %d\n", x, y);
}


在执行min(x++, y++)期间,x++和y++只执行了一次,因而结果是正确的。


三 、#define pr_debug(fmt,arg...) \

标准C只支持可变参数的函数,意味着函数的参数是不固定的,例如printf()函数
的原型为:
int printf( const char *format [, argument]... );
而在GNU C中,宏也可以接受可变数目的参数,例如:
#define pr_debug(fmt,arg...) \
printk(fmt,##arg)
这里arg 表示其余的参数可以是零个或多个,这些参数以及参数之间的逗号构成
arg 的值,在宏扩展时替换arg,例如下列代码:
pr_debug("%s:%d",filename,line)
会被扩展为:
printk("%s:%d", filename, line)

四、GCC下 特殊的数组声明

struct ast_app_option {
    unsigned int flag;
    unsigned int arg_index;
};
static const struct ast_app_option read_app_options[128] = {
    ['s'] = { .flag = OPT_SKIP },
    ['i'] = { .flag = OPT_INDICATION },
    ['n'] = { .flag = OPT_NOANSWER },
}
这段代码目的很灵巧,就是建立一个用字母做数组index的128位数数组,而且结构体的赋值直接用了{.flag=xxxxx}的简单方法,这是linux c特有的写法。
<think>嗯,用户想了解嵌入式C语言的语法手册。首先,我需要确定用户的需求是什么。他们可能是在进行嵌入式开发时遇到了不熟悉的语法,或者在阅读Linux内核代码时看到了一些奇怪写法,比如GNU C扩展的那些特性。根据提供的引用,尤其是引用[1]和[2],用户提到的嵌入式C语言可能主要是指GNU C在Linux内核中的扩展语法。 接下来,我需要整理这些扩展语法的常见内容。比如,属性声明(__attribute__)、语句表达式、变长数组、内联汇编等。这些都是标准C中没有的,但在嵌入式开发中常用的特性。例如,__attribute__((aligned(4)))用于内存对齐,这在嵌入式系统中对硬件操作很重要。 然后,用户可能需要具体的例子和解释,这样他们可以更好地理解和应用这些语法。比如,宏定义中的do{...}while(0)结构,这在内核代码中常见,可以避免宏展开后的错误。同时,内联汇编的部分需要说明如何在C代码中嵌入汇编指令,这在操作底层硬件时非常有用。 另外,用户可能还需要知道如何查阅相关的官方手册,比如GCC的文档,以及推荐的学习资源,如《Linux设备驱动程序》和《C语言深度解剖》。这些资料可以帮助他们深入学习。 还要注意引用提供的资料,确保回答中的信息准确并正确标注来源。例如,引用[1]提到GNU C扩展语法在标准教材中不常见,所以需要强调这些是编译器扩展的特性。引用[3]提到嵌入式开发中需注意代码的时序和硬件操作,这可能涉及到内存管理和位操作,这也是嵌入式C的重要部分。 最后,生成的相关问题需要围绕用户可能进一步关心的点,比如与标准C的区别、内存管理技巧、调试方法以及推荐的书籍。这些问题可以帮助用户更全面地掌握嵌入式C语言的应用。</think>### 嵌入式 C 语言语法要点解析 嵌入式 C 语言在标准 C 语法基础上,会根据硬件特性和编译器扩展增加特殊语法。以下是核心语法要点: #### 一、GNU C 扩展特性(Linux 内核常用) 1. **属性声明(Attribute)** 通过 `__attribute__` 定义变量/函数的特殊属性: ```c int var __attribute__((aligned(4))); // 4字节对齐 void func() __attribute__((noreturn)); // 函数无返回值 ``` 2. **语句表达式** 将代码块作为表达式使用: ```c #define max(x,y) ({ \ int _x = (x); \ int _y = (y); \ _x > _y ? _x : _y; }) ``` 3. **零长度数组(柔性数组)** 用于动态数据结构末尾: ```c struct packet { int len; char data[0]; // C99 后可用 char data[]; }; ``` 4. **内联汇编** 直接嵌入汇编指令: ```c __asm__ volatile("mov r0, #0"); ``` #### 二、硬件相关编程技巧 1. **寄存器位操作** 使用位域或位掩码: ```c #define LED_ON (1 << 5) GPIO_REG |= LED_ON; // 置位 GPIO_REG &= ~LED_ON; // 清零 ``` 2. **volatile 关键字** 禁止编译器优化硬件寄存器访问: ```c volatile uint32_t *reg = (uint32_t*)0x40020000; ``` 3. **内存屏障** 保证内存访问顺序: ```c __sync_synchronize(); // GCC 内置函数 ``` #### 三、嵌入式专用语法规范 1. **指定初始化(C99)** 结构体/数组的定向初始化: ```c struct point p = { .y = 5, .x = 2 }; int arr[5] = {[2] = 10, [4] = 20}; ``` 2. **编译断言(C11)** 静态断言检查: ```c _Static_assert(sizeof(int)==4, "int must be 4 bytes"); ``` #### 四、推荐学习资料 1. **官方手册** - 《GCC 官方文档》的 C 扩展章节 - 《ARM C 语言扩展规范》(针对 ARM 架构) 2. **经典书籍** - 《Linux 设备驱动程序》(O'Reilly)[^2] - 《C 语言深度解剖》(重点讲解底层实现) > 注意:不同编译器(如 Keil、IAR、GCC)支持的扩展语法存在差异,建议查阅对应编译器的《编译器用户指南》。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

turbocc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值