linux下的c编程

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

1.零长度数组

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

例如:

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

  char data[0]仅仅意味着程序中通过 var_data 结构体实例的 data[index]成员可以访问 len 之后的第 index 个地址,它并没有为 data[]数组分配内存,因此 sizeof(struct var_data)=sizeof(int)。假设 struct var_data 的数据域保存在 struct var_data 紧接着的内存区域,通过如下代码可以遍历这些数据:

  struct var_data s;

  ...
  for (i = 0; i < s.len; i++)
  {
    printf("%02x", s.data[i]);
  }

2.case范围

  GNUC 支持 casex…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':

3.语句表达式

  GNU C 把包含在括号中的复合语句看做是一个表达式,称为语句表达式,它可以出现在任何允许表达式的地方。 我们可以在语句表达式中使用原本只能在复合语句中使用的循环变量、局部变量等,例如:

  #define min_t(type,x,y) \
  ({ type _ _x = (x); type _ _y = (y); _ _x < _ _y ? _ _x: _ _y; })
  int ia, ib, mini;
  float fa, fb, minf;
  mini = min_t(int, ia, ib);
  minf = min_t(float, fa, fb);

  因为重新定义了_ _xx 和_ _y 这两个局部变量,所以以上述方式定义的宏将不会有副作用。在标准 C 中,对应的如下宏则会产生副作用:

  #define min(x,y) ((x) < (y) ? (x) : (y))
  代码 min(++ia,++ib)会被展开为((++ia) < (++ib) ? (++ia): (++ib)),传入宏的参数被增加两次

4.typeof 关键字

  typeof(x)语句可以获得 x 的类型,因此,我们可以借助 typeof 重新定义 min 这个宏:

  #define min(x,y) ({ \

  const typeof(x) _x = (x); \

  const typeof(y) _y = (y); \

  (void) (&_x ==&_y); \

  _x < _y ? _x : _y; })

  我们不需要像 min_t(type,x,y)这个宏那样把 type 传入,因为通过 typeof(x)、 typeof(y)可以获得 type。代码行(void) (&_x == &_y)的作用是检查_x 和_y 的类型是否一致。

5.可变参数的宏

  标准 C 只支持可变参数的函数, 意味着函数的参数是不固定的, 例如 printf()函数的原型为:

  int printf( const char *format [, argument]... );

  而在 GNUC 中,宏也可以接受可变数目的参数,例如:

  #define pr_debug(fmt,arg...) \
  printk(fmt,##arg)
  这里 arg 表示其余的参数可以是零个或多个,这些参数以及参数之间的逗号构成arg 的值,在宏扩展时替换 arg,例如下列代码:

  pr_debug("%s:%d",filename,line)
  会被扩展为:
  printk("%s:%d", filename, line)

  使用“##”的原因是理 arg 不代表任何参数的情况,这时候,前面的逗号就变得多余了。使用“##”之后,GNUC 预处理器会丢弃前面的逗号,这样,代码:

  pr_debug("success!\n")
  会被正确地扩展为:
  printk("success!\n")
  而不是:
  printk("success!\n",)

6.标号元素

  标准 C 要求数组或结构体的初始化值必须以固定的顺序出现,在 GNU C 中, 通过指定索引或结构体成员名,允许初始化值以任意顺序出现。

  指 定数 组 索 引的方 法是在 初 始 化 值 前 添加“ [INDEX]= ” , 当然也 可以 用“[FIRST … LAST]=” 的形式指定一个范围。 例如下面的代码定义一个数组,并把其中的所有元素赋值为 0:  unsigned char data[MAX] = { [0 ... MAX-1] 下面的代码借助结构体成员名初始化结构体:

  struct file_operations ext2_file_operations =
  {
    llseek: generic_file_llseek,
    read: generic_file_read,
    write: generic_file_write,
    ioctl: ext2_ioctl,
    mmap: generic_file_mmap,
    open: generic_file_open,
    release: ext2_release_file,
    fsync: ext2_sync_file,
  };
但是,Linux 2.6 推荐类似的代码应该尽量采用标准 C 的方式,如下所示:
  struct file_operations ext2_file_operations =
  {
    .llseek = generic_file_llseek,
    .read = generic_file_read,
    .write = generic_file_write,
    .aio_read = generic_file_aio_read,
    .aio_write = generic_file_aio_write,
    .ioctl = ext2_ioctl,
    .mmap = generic_file_mmap,
    .open = generic_file_open,
    .release = ext2_release_file,
    .fsync = ext2_sync_file,
    .readv = generic_file_readv,
    .writev = generic_file_writev,
    .sendfile = generic_file_sendfile,
  };
7.当前函数名
  GNU C 预定义了两个标志符保存当前函数的名字, _ _FUNCTION_ _保存函数在源码中的名字,_ _PRETTY_FUNCTION_ _保存带语言特色的名字。在 C 函数中,这两个名字是相同的。

  void example()
  {
    printf("This is function:%s", _ _FUNCTION_ _);
  }
  代码中的_ _FUNCTION_ _意味着字符串“example” 。

8.特殊属性声明

  GNU C 允许声明函数、变量和类型的特殊属性, 以便进行手工的代码优化和定制代码检 查 的 方法。 指 定 一个声 明 的属性 , 只 需 要在声 明 后 添加__ a tt r i b u t e __ (( AT T R I B U T E )) 。其中 A TTRIBUTE 为属性说明,如果存在多个属性,则以逗号分隔。GNU C 支持 noreturn、format、section、aligned、packed 等十多个属性。

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

  # define ATTRIB_NORET _ _attribute_ _((noreturn)) ....

  asmlinkage NORET_TYPE void do_exit(long error_code) ATTRIB_NORET;

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

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

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

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

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

  struct example_struct

  {

    char a;

    int b;

    long c;

  } _ _attribute_ _((aligned(4)));

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

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

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

9.内建函数

  GNUC 提供了大量的内建函数, 其中大部分是标准 C 库函数的 GNUC 编译器内建版本,例如 memcpy()等,它们与对应的标准 C 库函数功能相同。不属于库函数的其他内建函数的命名通常以_ _builtin 开始,如下所示。

  1. 内建函数__builtin_return_address (LEVEL)返回当前函数或其调用者的返回地址,参数 LEVEL 指定调用栈的级数,如 0 表示当前函数的返回地址,1 表示当前函数的调用者的返回地址。
  2. 内建函数_ _builtin_constant_p(EXP)用于判断一个值是否为编译时常数, 如果参数 EXP 的值是常数,函数返回1,否则返回0。
  3. 内建函数_ _builtin_expect(EXP , C)用于为编译器提供分支预测信息,其返回值是整数表达式 EXP 的值,C 的值必须是编译时常数。

例如,下面的代码检测第 1 个参数是否为编译时常数以确定采用参数版本还是非
参数版本的代码:
  #define test_bit(nr,addr) \
  (_ _builtin_constant_p(nr) ? \
  constant_test_bit((nr),(addr)) : \
  variable_test_bit((nr),(addr)))
10.  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)的使用完全是为了保证宏定义的使用者能无编译错误地使用宏,它不对其使用者做任何假设。

11.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设备驱动开发详解》P-73

linux下的c编程的更多相关文章

  1. Linux下TCP网络编程与基于Windows下C#socket编程间通信

    一.linux下TCP网络编程基础,需要了解相关函数 Socket():用于套接字初始化. Bind():将 socket 与本机上的一个端口绑定,就可以在该端口监听服务请求. Listen():使s ...

  2. Linux下的C编程实战

    Linux下的C编程实战(一) ――开发平台搭建 1.引言 Linux操作系统在服务器领域的应用和普及已经有较长的历史,这源于它的开源特点以及其超越Windows的安全性和稳定性.而近年来, Linu ...

  3. Linux下C语言编程实现spwd函数

    Linux下C语言编程实现spwd函数 介绍 spwd函数 功能:显示当前目录路径 实现:通过编译执行该代码,可在终端中输出当前路径 代码实现 代码链接 代码托管链接:spwd.c 所需结构体.函数. ...

  4. Linux基础与Linux下C语言编程基础

    Linux基础 1 Linux命令 如果使用GUI,Linux和Windows没有什么区别.Linux学习应用的一个特点是通过命令行进行使用. 登录Linux后,我们就可以在#或$符后面去输入命令,有 ...

  5. LINUX下C语言编程基础

    实验二 Linux下C语言编程基础 一.实验目的 1. 熟悉Linux系统下的开发环境 2. 熟悉vi的基本操作 3. 熟悉gcc编译器的基本原理 4. 熟练使用gcc编译器的常用选项 5 .熟练使用 ...

  6. LINUX下C语言编程调用函数、链接头文件以及库文件

    LINUX下C语言编程经常需要链接其他函数,而其他函数一般都放在另外.c文件中,或者打包放在一个库文件里面,我需要在main函数中调用这些函数,主要有如下几种方法: 1.当需要调用函数的个数比较少时, ...

  7. Linux下C语言编程基础学习记录

    VIM的基本使用  LINUX下C语言编程 用gcc命令编译运行C语言文件 预处理阶段:将*.c文件转化为*.i预处理过的C程序. 编译阶段:将*.i文件编译为汇编代码*.s文件. 汇编阶段:将*.s ...

  8. 【转】Linux基础与Linux下C语言编程基础

    原文:https://www.cnblogs.com/huyufeng/p/4841232.html ------------------------------------------------- ...

  9. 【转】 Linux下的多线程编程

    作者:gnuhpc 出处:http://www.cnblogs.com/gnuhpc/原文链接:http://www.cnblogs.com/gnuhpc/archive/2012/12/07/280 ...

随机推荐

  1. ionic 接触的第一个Hybrid项目

    最近需要维护一个Hybird项目,使用的是ionic,由于是第一个Hybrid项目,在这里记录下基本的知识. 先看一下ionic的最基本介绍: http://my.oschina.net/u/2275 ...

  2. pc/app 项目/功能设计

    2015-08-11 15:29:59 首先, 提供服务的整个系统包含哪几个设备 用户->[PC浏览器|APP|APP浏览器]->web服务器->[PHP/JAVA组件]->[ ...

  3. FastReport中文网

    FastReport中文网 http://www.fastreportcn.com/Article/2.html

  4. ACM/ICPC 之 Floyd练习六道(ZOJ2027-POJ2253-POJ2472-POJ1125-POJ1603-POJ2607)

    以Floyd解法为主的练习题六道 ZOJ2027-Travelling Fee //可免去一条线路中直接连接两城市的最大旅行费用,求最小总旅行费用 //Time:0Ms Memory:604K #in ...

  5. Win7下同时使用有线和无线时的优先级设置

    终于找到这个问题的解决方案了!!!!我是通过方法1改跃点数实现的,方法2无效. http://linshengling.blog.163.com/blog/static/114651912012102 ...

  6. 跟着 8 张思维导图学习 Javascript

    学习的道路就是要不断的总结归纳,好记性不如烂笔头,so,下面将po出8张javascript相关的思维导图. 思维导图小tips:思维导图又叫心智图,是表达发射性思维的有效的图形思维工具 ,它简单却又 ...

  7. SQL触发器中若取到null值可能引发的问题

    declare @code varchar(20), @cs varchar(20),@zc varchar(20)set @cs='('+@cs+'*'+@zc+')'print '字符'+@csi ...

  8. Greedy:Bound Found(POJ 2566)

       神奇密码 题目大意:就是给你一个数组,要你找出连续的数的绝对值的和最接近t的那一串,并且要找出数组的上界和下界的下标,并显示他们的和 因为这一题的数有正有负,所以必须要先把和求出来,然后排序,然 ...

  9. LightOJ 1234 Harmonic Number

    D - Harmonic Number Time Limit:3000MS     Memory Limit:32768KB     64bit IO Format:%lld & %llu S ...

  10. IOS - UITableView分批显示数据 实现点击加载更多

    Phone屏幕尺寸是有限的,如果需要显示的数据很多,可以先数据放到一个table中,先显示10条,table底部有一察看更多选项,点击察看更多查看解析的剩余数据.基本上就是数据源里先只放10条, 点击 ...