3.5 Linux下的C编程特点

3.5.1 Linux编码风格

Linux有独特的编码风格,Linux程序的命名习惯和Windows程序的命名习惯及著名的匈牙利命名法有很大的不同。在Windows程序中,习惯以如下方式命名宏、变量和函数声明:

#define PI 3.1415926     /* 用大写字母代表宏 */
int minValue, maxValue;  /* 变量:第一个单词全小写,其后单词的第一个字母大写 */

void SendData(void);     /* 函数声明:所有单词第一个字母都大写 */

这种命名方式在程序员中非常盛行,意思表达清晰且避免了匈牙利法的臃肿,单词之间通过首字母大写来区分。通过第1个单词的首字母是否大写可以区分名称属于变量还是属于函数,而看到整串的大写字母可以断定为宏。实际上,Windows的命名习惯并非仅限于Windows编程,许多领域的程序开发都遵照此习惯。

但是Linux不以这种习惯命名,对于上面的一段程序,在Linux中它会被命名为:

#define PI 3.1415926
int min_value, max_value;

void send_data(void);

在上述命名方式中,下划线大行其道,不按照Windows所采用的用首字母大写来区分单词的方式。Linux的命名习惯与Windows命名习惯各有千秋,编写Linux程序,代码风格理应与Linux开发社区保持一致。

Linux的代码缩进使用“TAB”。

Linux中代码括号“{”和“}”的使用原则如下。

1)对于结构体、if/for/while/switch语句,“{”不另起一行,例如:

struct var_data {
   int len;
   char data[0];
};
if (a == b) {
     a = c;
     d = a;
}
for (i = 0; i < 10; i++) {
     a = c;
     d = a;
}

2)如果if、for循环后只有1行,不要加“{”和“}”,例如:
for (i = 0; i < 10; i++) {
      a = c;
}

应该改为:
for (i = 0; i < 10; i++)
      a = c;

3)if和else混用的情况下,else语句不另起一行,例如:

if (x == y) {
    ...
} else if (x > y) {
    ...
} else {
  ...
}

4)对于函数,“{”另起一行,譬如:

int add(int a, int b)
{
     return a + b;

}

在switch/case语句方面,Linux建议switch和case对齐,例如:

switch (suffix) {
case 'G':
case 'g':
      mem <<= 30;
      break;
case 'M':
case 'm':
      mem <<= 20;
      break;
case 'K':
case 'k':
      mem <<= 10;
      /* fall through */
default:
      break;
}

内核下的Documentation/CodingStyle描述了Linux内核对编码风格的要求,内核下的scripts/checkpatch.pl提供了1个检查代码风格的脚本。

如果使用scripts/checkpatch.pl检查包含如下代码块的源程序:

for (i = 0; i < 10; i++) {
      a = c;
}

就会产生“WARNING:braces{}are not necessary for single statement blocks”的警告。

在工程阶段,一般可以在SCM(软件配置管理)软件的服务器端使能pre-commit hook,自动检查工程师提交的代码是否
符合Linux的编码风格,如果不符合,则自动拦截。git的pre-commit hook可以运行在本地代码仓库中,

3.5.2 GNU C与ANSI (标准C)C

Linux上可用的C编译器是GNU (自由软件基金会)C编译器,它建立在自由软件基金会的编程许可证的基础上,因此可以自由发布。GNU C对标准C进行一系列扩展,以增强标准C的功能。

1.零长度和变量长度数组

GNU C允许使用零长度数组,在定义变长对象的头结构时,这个特性非常有用。例如:

struct var_data {
   int len;
   char data[0];
};

char data[0]仅仅意味着程序中通过var_data结构体实例的data[index]成员可以访问len之后的第index个地址,它并没有为data[]数组分配内存,因此sizeof(struct var_data)= sizeof(int)。参见如下例子:

#include <stdio.h>

int main(int argc, char *argv[])
{
struct var_data {
int len;
char data[0];
};

printf("sizeof(struct var_data) = %d\n", sizeof(struct var_data));// 4
printf("sizeof(int) = %d\n", sizeof(int)); // 4

return 0;
}

GNU C中也可以使用1个变量定义数组,例如如下代码中定义的“double x[n]”:

int main (int argc, char *argv[])
{
    int i, n = argc;
    double x[n];
    for (i = 0; i < n; i++)
       x[i] = i;
    return 0;
}

2.case范围

GNU C支持case x…y这样的语法,区间[x,y]中的数都会满足这个case的条件,请看下面的代码:

switch (ch) {
case '0'... '9': c -= '0';
   break;
case 'a'... 'f': c -= 'a' - 10;
   break;
case 'A'... 'F': c -= 'A' - 10;
   break;
}

代码中的case'0'...'9'等价于标准C中的:

case '0': case '1': case '2': case '3': case '4':case '5': case '6': case '7': case '8': case '9':

8.特殊属性声明

GNU C允许声明函数、变量和类型的特殊属性,以便手动优化代码和定制代码检查的方法。要指定一个声明的属性,只需要在声明后添加__attribute__((ATTRIBUTE)) 。其中ATTRIBUTE为属性说明,如果存在多个属性,则以逗号分隔。GNU C支持noreturn、format、section、aligned、packed等十多个属性。

noreturn属性作用于函数,表示该函数从不返回。这会让编译器优化代码,并消除不必要的警告信息。例如:

#define ATTRIB_NORET  __attribute__((noreturn)) ....
asmlinkage NORET_TYPE void do_exit(long error_code) ATTRIB_NORET;

asmlinkage是限定词,asmlinkage 的定义(/usr/include/asm/linkage.h里面):

#define asmlinkage CPP_ASMLINKAGE  __attribute__((regparm(0)))
__attribute__是关键字,是gcc的C语言扩展,regparm(0)表示不从寄存器传递参数,如果是__attribute__((regparm(3))),那么调用函数的时候参数不是通过栈传递,而是直接放到寄存器里,被调用函数直接从寄存器取参数。

format属性也用于函数,表示该函数使用printf、scanf或strftime风格的参数,指定format属性可以让编译器根据格式串检查参数类型。例如:

asmlinkage int printk(const char * fmt, ...) __attribute__ ((format (printf, 1, 2)));

上述代码中的第1个参数是格式串,从第2个参数开始都会根据printf()函数的格式串规则检查参数。

unused属性作用于函数和变量,表示该函数或变量可能不会用到,这个属性可以避免编译器产生警告信息。

aligned属性用于变量、结构体或共用体,指定变量、结构体或共用体的对齐方式,以字节为单位,例如:

struct example_struct {
    char a;
    int b;
    long c;
} __attribute__((aligned(4)));

表示该结构类型的变量以4字节对齐。

printf("sizeof(struct example_struct) = %d\n", sizeof(struct example_struct)); // 16

struct example_struct {
    char a;
    int b;
    long c;
} ;

printf("sizeof(struct example_struct) = %d\n", sizeof(struct example_struct)); // 16

packed属性作用于变量和类型,用于变量或结构体成员时表示使用最小可能的对齐,用于枚举、结构体或联合体类型时表示该类型使用最小的内存。例如:

struct example_struct {
    char a;
    int b;
    long c  __attribute__((packed));
};

printf("sizeof(struct example_struct) = %d\n", sizeof(struct example_struct)); // 16 四个字节对齐

struct example_struct {
    char a;
    int b;
    long c;
} __attribute__((packed));

printf("sizeof(struct example_struct) = %d\n", sizeof(struct example_struct)); // 13

总结:编译器对结构体成员及变量对齐的目的是为了更快地访问结构体成员及变量占据的内存。例
如,对于一个32位的整型变量,若以4字节方式存放(即低两位地址为00),则CPU在一个总线周期内就可以读取32位;否则,CPU需要两个总线周期才能读取32位。

9.内建函数

GNU C提供了大量内建函数,其中大部分是标准C库函数的GNU C编译器内建版本,例如memcpy()等,它们与对应的标准C库函数功能相同。

不属于库函数的其他内建函数的命名通常以__builtin开始,如下所示。

内建函数__builtin_return_address(LEVEL)返回当前函数或其调用者的返回地址,参数LEVEL指定调用栈的级数,如0表示当前函数的返回地址,1表示当前函数的调用者的返回地址。

内建函数__builtin_constant_p(EXP)用于判断一个值是否为编译时常数,如果参数EXP的值是常数,函数返回1,否则返回0。

内建函数__builtin_expect(EXP,C)用于为编译器提供分支预测信息,其返回值是整数表达式EXP的值,C的值必须是编译时常数。

Linux内核编程时常用的likely()和unlikely()底层调用的likely_notrace()、unlikely_notrace()就是基于__builtin_expect(EXP,C)实现的。

#define likely_notrace(x)       __builtin_expect(!!(x), 1)
#define unlikely_notrace(x)     __builtin_expect(!!(x), 0)

若代码中出现分支,则即可能中断流水线,可以通过likely()和unlikely()暗示分支容易成立还是不容易成立,例如

if (likely(!IN_DEV_ROUTE_LOCALNET(in_dev)))
    if (ipv4_is_loopback(saddr))

goto e_inval;

在使用gcc编译C程序的时候,如果使用“-ansi–pedantic”编译选项,则会告诉编译器不使用GNU扩展语法。例如对于如下C程序test.c:

struct var_data {
   int len;
   char data[0];
};

struct var_data a;

直接编译可以通过:

gcc -c test.c

如果使用“-ansi–pedantic”编译选项,编译会报警:

gcc -c -pedantic -ansi test.c
test.c:3: warning: ISO C forbids zero-size array ‘data’ [-Wpedantic]

3.5.3 do{}while(0)语句

在Linux内核中,经常会看到do{}while(0)这样的语句,许多人开始都会疑惑,认为do{}while(0)毫无意义,因为它只会执行一次,加不加do{}while(0)效果是完全一样的,其实do{}while(0)的用法主要用于宏定义中。

用一个简单的宏来演示:

#define SAFE_FREE(p) do{ free(p); p = NULL;} while(0)

假设这里去掉do...while(0),即定义SAFE_DELETE为:#define SAFE_FREE(p) free(p); p = NULL;

那么以下代码:
if(NULL != p)
   SAFE_DELETE(p)
else
   .../* do something */

会被展开为:
if(NULL != p)
   free(p); p = NULL;  // 复合语句
else
   .../* do something */

展开的代码中存在两个问题:
1)因为if分支后有两个语句,导致else分支没有对应的if,编译失败。

2)假设没有else分支,则SAFE_FREE中的第二个语句无论if测试是否通过,都会执行。

的确,将SAFE_FREE的定义加上{}就可以解决上述问题了,即:

#define SAFE_FREE(p) { free(p); p = NULL;}

这样,代码:
if(NULL != p)
    SAFE_DELETE(p)
else
    ... /* do something */
会被展开为:

if(NULL != p)
   { free(p); p = NULL; }
else
    ... /* do something */

但是,在C程序中,在每个语句后面加分号是一种约定俗成的习惯,那么,如下代码:

if(NULL != p)
    SAFE_DELETE(p);
else
    ... /* do something */

将被扩展为:
if(NULL != p)
   { free(p); p = NULL; };
else
   ... /* do something */

这样,else分支就又没有对应的if了,编译将无法通过。假设用了do{}while(0)语句,情况就不一样了,同样的代码会被展开为:

if(NULL != p)
    do{ free(p); p = NULL;} while(0);
else
    ... /* do something */

而不会再出现编译问题。do{}while(0)的使用完全是为了保证宏定义的使用者能无编译错误地使用宏,它不对其使用者做任何假设。

3.5.4 goto语句

用不用goto一直是一个著名的争议话题,Linux内核源代码中对goto的应用非常广泛,但是一般只限于错误处理中,其结构如:

if(register_a()!=0)
     goto err;
if(register_b()!=0)
     goto err1;
if(register_c()!=0)
     goto err2;
if(register_d()!=0)
     goto err3;
...
err3:
    unregister_c();
err2:
    unregister_b();
err1:
    unregister_a();
err:
    return ret;

这种将goto用于错误处理的用法实在是简单而高效,只需保证在错误处理时注销、资源释放等,与正常的注册、资源申请顺序相反。

Linux内核及内核编程之五Linux下的C编程特点相关推荐

  1. c语言编程操作系统,操作系统下c语言编程入门.doc

    操作系统下c语言编程入门 linux操作系统下c语言编程入门 1)Linux程序设计入门--基础知识? 2)Linux程序设计入门--进程介绍? 3)Linux程序设计入门--文件操作? 4)Linu ...

  2. (6)Windows下的网络编程

    目录索引 Windows下的网络编程 接口分析 编程差异 编程示例 多路复用 Windows下的网络编程 接口分析 Windows下的socket编程接口与Linux中几乎相同: 不同之处: – 返回 ...

  3. 【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】Oops在Linux 2.6内核+PowerPC架构下的前世今生

    Oops在Linux 2.6内核+PowerPC架构下的前世今生 Sailor_forever  sailing_9806#163.com (本原创文章发表于Sailor_forever 的个人blo ...

  4. linux内核_Linux内核编程风格简介

    01不同的语言风格 当今世界上一共有5000多种语言.不同的国家.民族和地区,说着不同的语言.每个民族不一定有自己的文字,但一般都会有自己的语言.即使是同一种语言,在不同的时代.不同的地区和群体,说话 ...

  5. Linux设备驱动开发详解-Note(5)---Linux 内核及内核编程(1)

    Linux 内核及内核编程(1) 成于坚持,败于止步 Linux 2.6 内核的特点 Linux 2.6 相对于 Linux 2.4 有相当大的改进,主要体现在如下几个方面. 1.新的调度器 2.6 ...

  6. Linux 中断学习之小试牛刀篇---Linux中断内核编程

    原址 前言 在前面分析了中断的基本原理后,就可以写一个内核中断程序来体验以下,也可以借此程序继续深入来了解内核中断的执行过程 一.内核中断程序: 我们还是来看一看成程序: 在看程序之前,要熟悉如何进行 ...

  7. 第3章--Linux内核及内核编程

    一.源代码目录结构 /arch:包含和硬件体系结构相关的代码,每种平台占一个相应的目录. /block:块设备驱动程序I/O调度. /crypto:常见加密和散列算法,以及一些压缩和CRC校验算法. ...

  8. Linux设备驱动开发详解【三】_Linux 内核及内核编程

    本文简介 由于 Linux 驱动编程本质属于 Linux 内核编程,因此有必要掌握 Linux 内核及内核编程的基础知识.         3.1-3.2 节讲解 Linux 内核的演变及 Linux ...

  9. linux核能软件,ARM big.LITTLE大小核架构在Linux和Android内核下多核调度算法

    在2013年,big.LITTLE家族又增加了新的SoC实现,有2个Cortex-A15+3个Cortex-A7核的ARM的参考测试芯片TC2,以及在三星Galaxy S4手机中应用的Samsung- ...

最新文章

  1. flask + react_再写一本 Flask 书
  2. 如何提高用户注册转化与用户激活
  3. Java 堆内存模型
  4. 让VC编译的程序链接到系统的 msvcrt.dll 的方法
  5. mybatis学习(53):构造方法映射
  6. 【转】程序员技术练级攻略
  7. python画圆形螺旋线_中秋节到了,送你一个Python做的Crossin牌“月饼”
  8. 类和对象编程(五):友元函数
  9. hive 操作(四)
  10. Service Worker 全面进阶
  11. 【优化求解】基于matlab GUI模拟退火算法区域通信网频率规划【含Matlab源码 933期】
  12. 苹果14手机怎么投屏到mac电脑上面?
  13. 【教程】Gentoo的安装
  14. OpenGL编程(四)改变窗口大小时保持图形的原形
  15. android串口开发!一年后斩获腾讯T3,附面试题答案
  16. 抖音挑战微信能赢么?
  17. 东华考研复试21-30oj
  18. 微信小程序版的登录注册
  19. 世界上第一台数字计算机图片大全,第二章 计算机中的图世界
  20. 软件工程知识点总结汇总

热门文章

  1. 华为存储s2600t配置上架
  2. SEO优化需要时间是多久?
  3. 企业如何正确走好SEO
  4. 【小家java】java9新特性(简述十大新特性) 褒贬不一
  5. Java综合性实验 - 学生成绩分析程序
  6. i.MX6ULL开发板【终结者】硬件资源说明
  7. RXBUS的简单使用
  8. 220V转12V成熟设计 6W,包含原理图和pcb,附芯片手册,包含变压器设计
  9. 企业微信开发文档接口API获取accesstoken和读取成员内部员工
  10. 零基础学小程序 —— 模板样式(三)