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高可用解决方案

    MySQL 是一种关系数据库管理系统,关联数据库将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性.MySQL 软件采用了双授权政策(本词条"授权政策& ...

  2. ios 利用size classes 使 iPad  水平和垂直方向布局不同

    我们知道ipad全屏幕显示时,无论水平放置还是竖直放置,width 和 height 都是 regular,不像iphone能够区别,那么就不能使用size class 布局不同的水平和垂直界面了吗? ...

  3. Solr集群更新配置的方式

    solr集群中配置文件是经常更新的,频率最高的也就是schema.xml和solrconfig.xml这两个配置文件了,对于更新配置文件之前,我们先了解一下集群项目结构 由于在集群模式下,solrco ...

  4. Java for LeetCode 229 Majority Element II

    Given an integer array of size n, find all elements that appear more than ⌊ n/3 ⌋ times. The algorit ...

  5. 用 get 同步/异步 方式获取网络数据并输出

    //同步请求 //创建NSString用来存储请求的网址 NSString* str=@"http://v.juhe.cn/weather/index?format=2&cityna ...

  6. LeetCode 263 Ugly Number

    Problem: Write a program to check whether a given number is an ugly number. Ugly numbers are positiv ...

  7. 20145213《Java程序设计》第七周学习总结

    20145213<Java程序设计>第七周学习总结 教材学习内容总结 周末快乐的时间总是短暂的,还没好好感受就到了要写博客的周日.有人喟叹时间都去哪儿了,那本周我们就来认识一下Java里的 ...

  8. ajax基础语法、ajax做登录、ajax做用户名验证是否可用、ajax做关键字查询动态显示、ajax做用表格显示数据并增加操作列

    AJAX: AJAX 是一种用于创建快速动态网页的技术. 通过在后台与服务器进行少量数据交换,AJAX 可以使网页实现异步更新.这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新.   ...

  9. C++静态代码分析工具对比cppCheck与PreFast

    具体内容参看文件<CppCheck和PreFast对Cplusplus代码静态分析测试.zip> C++测试源代码main.cpp #define NULL 0 #include < ...

  10. EF性能调优

    首先说明下: 第一次运行真是太慢了,处理9600多个员工数据,用了81分钟!! 代码也挺简单,主要是得到数据-->对比分析-->插入分析结果到数据库.用的是EF的操作模式. public ...