各种C语言标准与区别

C 语言标准

C语言作为一种常用的计算机开发语言,被用于各种领域,尤其是单片机与嵌入式的开发,在现阶段更有着无可替代的作用,我也经常会使用C语言进行各种开发,使用过程中我们经常会听说 GUN C 、ANSI C、标准C这几个术语,在了解这几个术语对应的C标准之前,我们需要先了解三个组织,分别是自由软件基金会,美国国家标准学会以及国际标准化组织。下面对其三个组织进行简单说明:

  • 自由软件基金会:是美国的一个民间非营利组织,致力于推进自由软件,其中linux与gnu就是由这个组织在维护。
  • 美国国家标准学会:听名字就知道这是美国用于制定自己国家标准各类标准的组织(政府组织)
  • 国际标准化组织:作用同美国国家标准协会相似,只是这个组织的目标更远大一些,致力于制定国际标准。
  • 其中GNU C 是软件自由基金会制定的标准,ANSI C 是由美国国家标准学会制定的标准,而ISO C则是由国际标准化组织制定的标准,当前是一个国际化的时代,都在追求标准,那么这个标准化肯定不能是某一个国家的标准,那么这个标准肯定要有国际标准化组织来制定,所以我们常说的标准C其实就是ISO C,下面统称为标准C

    大约在90年代,美国国家标准学会与国际标准化组织相互接纳吸收对方的标准,(ANSI C与标准C的恩怨情仇大家可以自行搜索),所以当前标准C 与ANSI C的标准其实是一样的。GNU C 主要应用于linux开发,比标准c支持更多的特性,使用起来更加灵活(个人观点),所以 标准C = ISO C = ANSI C ≠ GNU C


    GNU C与标准C的区别

    下面介绍GNU C 与标准C的区别,其实主要是介绍GNU C 的特殊用法

  • GNU C 允许零长度数组
    GNU C 允许定义长度为0的数组,可能很多人会问长度为0的数组有什么用啊。其实我们可以定义一个长度为0的数组,那么这个数组是不占用内存空间的,但是 我们可以通过这个数组来访问数组后面的数据,比如一个结构体:

    struct data_pra
    {
    char name;
    char num[0];
    char year;
    charr month;
    ...
    };
    struct data_pra data;

    就可以通过调用data.num[0]获得year数据,data.num[1]来获得month的数据。如果他们的类型相同,并且取值范围相同,我们要对他们进行范围检查,这是我们就可以通过for循环来实现,而不需要获取每个数据的值,再进行取值范围判断了。
  • GNU支持case 取值范围用法
    GNU C 支持case x…y 语法,区间[x,y]的数都会满足这个case的条件,我们在不考虑if判断的情况下,如整数a的取值范围为0-5,当0<3时执行fun1函数,大于等于3时执行fun2();则GNU C可以通过以下代码实现:

    switch(a)
    {
    case 0..: 2:fun1();
    break;
    case 3…5: fun2();
    break;
    }

    而不是

    switch(a)
    {
    case 0
    case 1;
    case 2:
    fun1();
    break;
    case 3:
    case 4;
    case 5:
    fun2();
    break;
    }

    如果条件越多,这种实现方案就越简单方便。也便于其他人员阅读代码。
  • 语句表达式
    GNU C把包含在括号里的复合语句看做是一个表达式,称为语句表达式,它可以出现在任何允许表达式的地方。可以在语句表达式中使用原本只能在复合语句中使用的循环变量、局部变量等,例如:

    #define min_t(type,x,y) ({type __x=(x); type __y=(y);__x<__y?__x:__y})
    int ia,ib,mini;
    mini=min_t(int,ia,ib);

    这样,因为重新定义了__x和__y这两个局部变量,所以上述方法定义的宏将不会有副作用。在标准C中,对应的宏通常会有副作用
  • typeof关键字
    typeof(x) 可以获得x的类型,因此,可以借助typeof重新定义上一条提到的min_t这个宏

    #define min(x,y) /
    ({ /
    const typeof(x) _x=(x);/
    const typeof(y) _y=(y);/
    (void) (&_x==&_y);/
    _x<_y ? _x: _y ; })

    不需要像上一条时那样传一个type进去,因为通过typeof(x)可以得到type。
    代码 (void) (&_x==&_y);的作用是检查_x和_y的类型是否一致。
  • 可变参数的宏
    标准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);

    使用##的原因是为了处理arg不代表任何参数的情况,这时候,前面的逗号就变得多余了。

    使用##之后,GNU C预处理器会丢弃前面的逗号
    pr_debug(“success!/n”) 会被正确扩展为 printk(“success!/n”)
    而不是 printk(“success!/n”,);

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

    指定数组索引的方法是在初始化值前添加 [INDEX]= ,当然也可以用 [FIRST…LAST]= 的形式指定一个范围。例如下面的代码定义一个数组,并把其中的所有元素赋值为0:

    unsigned char data[MAX] ={[0...MAX-1]=0 };

    下面的代码借助结构体成员名初始化结构体:

    struct file_operations DEMO_fops = {
    owner : THIS_MODULE,
    llseek: DEMO_llseek,
    read: DEMO_read,
    write: DEMO_write,
    ioctl: DEMO_ioctl,
    open: DEMO_open,
    release: DEMO_release,
    };

    但是Linux 2.6还是推荐采用标准C的方式,如下

    struct file_operations DEMO_fops = {
    .owner = THIS_MODULE,
    .llseek = DEMO_llseek,
    .read = DEMO_read,
    .write = DEMO_write,
    .ioctl = DEMO_ioctl,
    .open = DEMO_open,
    .release = DEMO_release,
    };

  • 当前函数名
  • GUN C预定义了两个标识符保存当前的函数名,__FUNCTION__保存函数在源码中的名字,
    __PRETTY_FUNCTION__保存带语言特色的名字。在c函数中,这两个名字是相同的。

    void example()
    {
    printf(“This is function: %s “,FUNCTION);
    }

    代码中的FUNCTION意味着字符串”example”

  • 特殊属性声明
  • GNU C允许声明函数、变量和类型的特殊属性,以便进行手工的代码优化和定制代码检查的方法。指定一个声明的属性,只需要在申明后添加
    __attribute__((ATTRIBUTE))

    其中ATTRIBUTE为属性说明,如果存在多个属性,则以逗号分隔。GNU C支持noreturn format section aligned packed等十多个属性

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

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

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

    asmlinkage int printk(const char * fmt,…)/
    attribute((format(printf,1,2)));

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

    4、aligned属性指定结构体、变量、联合体的对齐方式。packed属性作用于变量和类型,表示压缩结构体,使用最小的内存。

    struct examprl_struct
    {
    char a;
    int b;
    long c;
    }attribute((packed));

    注意,这个attribute((packed))只能用在GNU C

  • 内建函数
  • GNU C 提供了大量的内建函数,其中很多是标准 C 库函数的内建版本,例如memcpy,它们与对应的 C 库函数功能相同,本文不讨论这类函数,其他内建函数的名字通常以 __builtin 开始。

    * __builtin_return_address (LEVEL)

    内建函数 __builtin_return_address 返回当前函数或其调用者的返回地址,参数LEVEL 指定在栈上搜索框架的个数,0 表示当前函数的返回地址,1 表示当前函数的调用者的返回地址,依此类推。例如:

    ++++ kernel/sched.c
    printk(KERN_ERR “schedule_timeout: wrong timeout ”
    “value %lx from %p/n”, timeout,
    __builtin_return_address(0));
    * __builtin_constant_p(EXP)

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

    ++++ include/asm-i386/bitops.h
    #define test_bit(nr,addr) /
    (__builtin_constant_p(nr) ? /
    constant_test_bit((nr),(addr)) : /
    variable_test_bit((nr),(addr)))

    很多计算或操作在参数为常数时有更优化的实现,在 GNU C 中用上面的方法可以根据参数是否为常数,只编译常数版本或非常数版本,这样既不失通用性,又能在参数是常数时编译出最优化的代码。

    • __builtin_expect(EXP, C)

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

    ++++ include/linux/compiler.h
    #define likely(x) __builtin_expect((x),1)
    #define unlikely(x) __builtin_expect((x),0)
    ++++ kernel/sched.c
    if (unlikely(in_interrupt())) {
    printk(”Scheduling in interrupt/n”);
    BUG();
    }

    这个内建函数的语义是 EXP 的预期值是 C,编译器可以根据这个信息适当地重排语句块的顺序,使程序在预期的情况下有更高的执行效率。上面的例子表示处于中断上下文是很少发生的,第 565-566 行的目标码可能会放在较远的位置,以保证经常执行的目标码更紧凑。

    猜你喜欢

    转载自blog.csdn.net/lzhitwh/article/details/79846113