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. MySQL排序原理与MySQL5.6案例分析【转】

    本文来自:http://www.cnblogs.com/cchust/p/5304594.html,其中对于自己觉得是重点的加了标记,方便自己查阅.更多详细的说明可以看沃趣科技的文章说明. 前言    ...

  2. SqlDateTime 溢出。

    SqlDateTime 溢出.必须介于 1/1/1753 12:00:00 AM 和 12/31/9999 11:59:59 PM 之间 解决方法:不要怀疑自己的判断就是数据库字段里的datatime ...

  3. ABAP 行列稳定刷新语句

    DATA stbl TYPE lvc_s_stbl. stbl-row = 'X'." 基于行的稳定刷新         stbl-col = 'X'." 基于列稳定刷新      ...

  4. FindinFiles - Windows文件内查找插件

    FindInFiles for Windows 今天分享一个不错的插件工具:FindInFiles.如其名,其功能和Visual Studio的Ctrl+H快捷键类似,方便Windows使用者在资源管 ...

  5. VC++ 判断当前系统为32位还是64位

    尝试了在VC++环境下判断系统为32位还是64位的方法,亲测有效!提供的函数如下 BOOL IsWow64() { typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) ...

  6. selinux

    root@lujie ~]# vim /etc/sysconfig/selinux # This file controls the state of SELinux on the system. # ...

  7. 如何让数据库在每天的某一个时刻自动执行某一个存储过程或者某一个sql语句

    这就要涉及到代理的知识了哦,首先我们要启动代理服务.

  8. sql的优化相关问题

    这篇文章写的真心不错,值得仔细拜读,所以将其转载过来了. 一.             分析阶段 一 般来说,在系统分析阶段往往有太多需要关注的地方,系统各种功能性.可用性.可靠性.安全性需求往往吸引 ...

  9. August 9th 2016, Week 33rd Tuesday

    Tomorrow is never clear, our time is here. 明天是未知的,我们还是要过好当下. Tomorrow is not unpredictable, it is cl ...

  10. 实现 Bootstrap 基本布局

    看到了一篇 20 分钟打造 Bootstrap 站点的文章,内容有点老,重新使用 Bootstrap3 实现一下,将涉及的内容也尽可能详细说明. 1. 创建基本的页面 我们先创建一个基本的 HTML ...