当前位置: 首页>行业 >

【进阶】GCC对C语言扩展语法归纳详解(二)

来源: 面包芯语 | 时间: 2023-07-29 08:26:59 |

扫描关注一起学嵌入式,一起学习,一起成长

不同编译器,由于开发环境、硬件平台、性能优化的需要,除了支持C语言标准,还会自己做一些扩展。


(资料图片仅供参考)

本系列文章,归纳总结 GCC 编译器对C语言标准的扩展语法,并逐一进行讲解。

前期文章:

GNU C扩展语法归纳详解(一)

接下来继续讲解其他扩展语法内容。

case 范围

上一篇文章,在讲解指定初始化数组元素的时候,介绍过用 ...实现索引范围。

GNU C支持使用...表示范围扩展,这个特性不仅可以使用在数组初始化中,也可以使用在switch-case语句中,如下面的程序 :

#include int main(void){int i = 4;switch(i){ case 1: printf("1\n");  break; case 2 ... 8:  printf("%d\n", i);  break; case 9:  printf("9\n");  break; } return 0;}

在这个程序中,如果当case值为2~8时,都执行相同的case分支,我们就可以通过 case 2...8:的形式来简化代码。

需要注意的是,...和两端的数据范围之间要有空格,否则会编译报错。

零长度数组

零长度数组,顾名思义,零长度数组就是长度为0的数组。

ANSI C标准规定:定义一个数组时,数组的长度必须是一个常数,即数组的长度在编译的时候是确定的。

C99标准规定:可以定义一个变长数组。也就是说,数组的长度在编译时是未确定的,在程序运行的时候才确定,甚至可以由用户指定大小。

int len;int a[len];

除此之外,GNU C 还支持零长度数组。其定义如下:

int a[0];

零长度数组有一个奇特的地方,就是它不占用内存存储空间。当使用sizeof查看其大小,通过运行结果可以看到,零长度数组在内存中不占用存储空间,长度大小为0。

零长度数组一般单独使用的机会很少,它常常作为结构体的一个成员,构成一个变长结构体。

在一个变长结构体中,零长度数组不占用结构体的存储空间,但是我们可以通过使用结构体的成员 a 去访问内存,非常方便。变长结构体的使用示例如下:

struct buffer{ int len; int a[0];}int main(void){ struct buffer *buf; buf = (struct buffer *)malloc(sizeof(struct buffer)+20); buf->len = 20; strcpy(buf->a, "hello yiqixue!\n"); puts(buf->a); free(buf); return 0;}

在这个程序中 , 我们使用 malloc 申请一片内存 ,大小为 sizeof(buffer)+20,即 24 字节,其中 4 字节为结构体buffer的大小。

成员变量 len 用来表示内存的长度 20,20字节空间是我们使用的内存空间。我们可以通过结构体成员a直接访问这片内存。

关于零长度数组的应用可以参考:C语言中长度为零的数组详解。

指针与零长度数组

思考一个问题:为什么不使用指针来代替零长度数组?

大家可能常常听到:数组名在作为函数参数进行参数传递时,就相当于一个指针。注意,数组名在作为参数传递时,传递的确实是一个地址,但数组名绝不是指针,两者不是同一个东西。

数组名用来表征一块连续内存空间的地址,而指针是一个变量,编译器要给它单独分配一个内存空间,用来存放它指向的变量的地址。

看一段示例程序:

struct buffer1{ int len; int a[0];};struct buffer2{ int len; int *a;};int main(void){ printf("buffer1:%d\n", sizeof(struct buffer1)); printf("buffer2:%d\n", sizeof(struct buffer2)); return 0;}

在32位CPU平台上,这段程序的运行结果为:

buffer1: 4buffer2: 8

对于一个指针变量,编译器要为这个指针变量单独分配一个存储空间,然后在这个存储空间上存放另一个变量的地址。

而对于数组名,编译器不会再给它分配一个单独的存储空间,它仅仅是一个符号,和函数名一样,用来表示一个地址 。

那么为什么不用指针,而用零长数组呢?如果使用指针,指针本身占用存储空间不说,它远远没有零长度数组用得巧妙,零长度数组不会对结构体定义造成冗余,而且使用起来很方便。

扩展关键字 __attribute__

GNU C 增加了一个 __attribute__关键字用来声明一个函数、变量或类型的特殊属性。

声明这个特殊属性的主要用途就是,指导编译器在编译程序时进行特定方面的优化或代码检查。例如,我们可以通过属性声明来指定某个变量的数据对齐方式。

__attribute__的用法非常简单,当我们定义一个函数、变量或类型时,直接在它们名字旁边添加下面的属性声明即可:

__attribute__((ATTRIBUTE))

需要注意的是,__attribute__后面是两对小括号,不能只写一对,否则编译就会报错。括号里面的 ATTRIBUTE 表示要声明的属性。

目前 __attribute__支持十几种属性声明:

section.

aligned.

packed.

format.

weak.

alias.

noinline.

always_inline.

……

在这些属性中,aligned 和 packed 用来显式指定一个变量的存储对齐方式。在正常情况下,当我们定义一个变量时,编译器会根据变量类型给这个变量分配合适大小的存储空间,按照默认的边界对齐方式分配一个地址。

而使用 __atttribute__这个属性声明,就相当于告诉编译器:按照我们指定的边界对齐方式去给这个变量分配存储空间 。

char c2 __attribute__((aligned(8))) = 4;int val __attribute__((section(".data")));

有些属性可能还有自己的参数。如 aligned(8) 表示这个变量按 8 字节地址对齐,属性的参数也要使用小括号括起来。

如果属性的参数是一个字符串,则小括号里的参数还要用双引号引起来。

当然,我们也可以对一个变量同时添加多个属性说明。在定义变量时,各个属性之间用逗号隔开:

char c2 __attribute__((packed,aligned(4)));char c2 __attribute__((packed,aligned(4))) = 4;__attribute__((packed,aligned(4))) char c2 = 4;

以上示例中,我们对一个变量添加两个属性声明,这两个属性都放在 __attribute__(())的两对小括号里面,属性之间用逗号隔开。

注意,属性声明要紧挨着变量,上面的三种声明方式都是没有问题的。

面的声明方式在编译的时候可能就通不过:

char c2 = 4 __attribute__((packed,aligned(4)));

属性声明 section

section 属性的主要作用是:在程序编译时,将一个函数或变量放到指定的段,即放到指定的section中。

一个可执行文件主要由代码段、数据段、BSS段构成。代码段主要存放编译生成的可执行指令代码,数据段 和 BSS 段用来存放全局变量、未初始化的全局变量。代码段、数据段和 BSS 段构成了一个可执行文件的主要部分。

除了这三个段,可执行文件中还包含其他一些段。用编译器的专业术语讲,还包含其他一些 section,如只读数据段、符号表等 。

参考 深入理解 C 语言的 hello world

在 Linux 环境下,使用 GCC 编译生成一个可执行文件 a.out,使用 readelf 命令,就可以查看这个可执行文件中各个 section 的基本信息,如大小、起始地址等。

在这些 section 中,.text section 就是我们常说的代码段;.data section 是数据段;.bss section 是 BSS 段。

在 GNU C 中,我们可以通过 __attribute__的 section 属性,显式指定一个函数或变量,在编译时放到指定的 section里面。

我们知道,未初始化的全局变量默认是放在 .bss section 中的,即默认放在 BSS 段中。现在我们就可以通过 section 属性声明,把这个未初始化的全局变量放到数据段 .data 中:

int val = 8;int val_n  __attribute__((section(".data")));int main(void){ return 0;}

属性声明 aligned

GNU C通过 __attribute__ 来声明 aligned 和 packed 属性,指定一个变量或类型的对齐方式。

这两个属性用来告诉编译器:在给变量分配存储空间时,要按指定的地址对齐方式给变量分配地址。

如果你想定义一个变量,在内存中以8字节地址对齐,就可以这样定义:

int a __attribute__((aligned(8));

通过aligned属性,我们可以显式地指定变量a在内存中的地址对齐方式。

注意,aligned 有一个参数,表示要按几字节对齐,地址对齐的字节数必须是 2 的幂次方,否则编译就会出错。

通过 aligned 属性声明,虽然可以显式地指定变量的地址对齐方式,但是也会因边界对齐造成一定的内存空洞,浪费内存资源。

按照地址对齐的一个原因为:这种对齐设置可以简化 CPU 和内存 RAM 之间的接口和硬件设计。

对于 32 位的计算机系统来说,在CPU读取内存时,硬件设计上可能只支持 4 字节或 4 字节倍数对齐的地址访问,CPU每次向内存 RAM 读写数据时,一个周期可以读写 4 字节。

结构体对齐

编译器在给一个结构体变量分配存储空间时,不仅要考虑结构体内各个基本成员的地址对齐,还要考虑结构体整体的对齐。为了结构体内各个成员地址对齐,编译器可能会在结构体内填充一些空间;为了结构体整体对齐,编译器可能会在结构体的末尾填充一些空间 。

详细可参考:结构体大小 你算对了吗

根据结构体的对齐规则,结构体的整体对齐要按结构体所有成员中最大对齐字节数或其整数倍对齐,或者说结构体的整体长度要为其最大成员字节数的整数倍,如果不是整数倍则要补齐 。

指定结构体某个成员变量的地址对齐方式,成员变量 b 按照 4 字节对齐:

struct data{ char a; short b __attribute__((aligned(4))); int c;};

该结构体大小为 12 字节。这是因为,我们显式指定 short 变量以 4 字节地址对齐,导致变量 a 的后面填充了 3 字节空间。int型变量 c 也要4字节对齐,所以变量b的后面也填充了2字节,导致整个结构体的大小为12字节。

显式指定整个结构体的对齐方式:

struct data{ char a; short b; int c;} __attribute__((aligned(16)));

这个结构体成员共占 8 个字节。正常来说,整个结构体的对齐只要按最大成员的对齐字节数(4字节)对齐即可。

但是,经过显式指定结构体整体以 16 字节对齐后,编译器就会在这个结构体的末尾填充 8 字节以满足 16 字节对齐的要求,最终导致结构体的总长度变为16字节。

注意,我们通过这个属性声明,其实只是建议编译器按照这种大小地址对齐,但不能超过编译器允许的最大值。编译器对每个基本数据类型都有默认的最大边界对齐字节数。若超过了,则编译器只能按照它规定的最大对齐字节数来给变量分配地址。

属性packed

packed 属性一般用来减少地址对齐,指定变量或类型使用最可能小的地址对齐方式。

struct data{ char a; short b __attribute__((packed)); int c __attribute__((packed));};

上例中,结构体成员 b 和 c 使用 packed 属性,告诉编译器尽量使用最小的地址(1字节)对齐分配地址空间。这个结构体大小为 7 字节。

也可以对整个结构体添加 packed 属性,这和分别对每个成员添加packed属性效果是一样的:

struct data{ char a; short b; int c;}__attribute__((packed));
struct data{ char a; short b; int c;}__attribute__((packed,aligned(8)));

在上面的程序中,结构体内所有成员所占的存储空间为 7 字节,成员之间没有空洞。

使用 aligned(8) 指定结构体按 8 字节地址对齐,所以编译器要在结构体后面填充 1 字节,这样整个结构体的大小就变为8字节,按8字节地址对齐。

好了,今天到这,感谢阅读,加油~

觉得文章不错,点击“分享”、“赞”、“在看” 呗!

关键词:

 

热文推荐

【进阶】GCC对C语言扩展语法归纳详解(二)

扫描关注一起学嵌入式,一起学习,一起成长不同编译器,由于开发环境、

2023-07-29

巨亏360亿!地产巨头宣布:复牌

中国基金报记者文夕因前期暴雷而导致停牌的内房股,近期陆续宣布计划复

2023-07-29

管城回族区市场监督管理局联合郑州市消费者权益保护中心在河南国香茶城举办郑州市消费教育基地授牌仪式

中原网讯(徐宁通讯员沙静)7月28日上午,管城回族区市场监督管理局联合

2023-07-29

中央纪委国家监委召开动员会 部署纪检监察机关配合开展全国医药领域腐败问题集中整治

据中央纪委国家监委网站消息,7月28日,纪检监察机关配合开展全国医药

2023-07-29

字体颜色代码完整文库 字体颜色代码

1、 R-表示后面的字体为红色(red) G-表示后面的字体为绿色(green) B-表

2023-07-29

大运“成都时刻”诠释的文化自信

7月28日,大运开幕夜,激情绽放时。一块石磬敲响悠悠古蜀回声,让古韵

2023-07-28

中国在线旅游平台:暑期预计约200万儿童购买“人生第一张机票”

中新社北京7月28日电 (记者 刘亮)暑期的到来,让更多孩子有机会出

2023-07-28

续集回归 ,《大宋少年志2》定档7月29日

电视剧《大宋少年志2》今日官宣定档7月29日。该剧由芒果TV、湖南卫视、

2023-07-28

赵刚:现代教育制度需要家校合作

中国网 中国发展门户网讯(记者刘梦雅)7月21日至23日,以“涵养家国情

2023-07-28

谢楠主持《巨齿鲨2》首映 吴京深情表白:我需要的时候,她一直都在

搜狐娱乐讯(哈麦 文马森 图)7月28日,吴京、杰森·斯坦森主演的《巨

2023-07-28

V观财报|领湃科技“80后”董事长叶善锦辞职 任期内股价腰斩

湖南领湃科技股份有限公司(领湃科技)28日盘后公告,董事会于近日收到公

2023-07-28

鼎秀齿科西安首届美学正畸节暨美学正畸示范机构签约启幕

7月27日,备受瞩目的“美学正畸示范机构签约”暨“鼎秀齿科西安首届美

2023-07-28

东方忘却录 第七部分

后置的结局更新时间:2012-5-127:06:10字数:5447清晨,就像是时间被再

2023-07-28

刘恺威宣布杨幂怀二胎(杨幂有儿子吗)

目前只有一个2014年1月8日,杨幂与刘恺威在巴厘岛举办了结婚典礼,而此

2023-07-28

重庆银行(601963.SH):增持主体累计增持381.83万股 增持已实施完毕

格隆汇7月28日丨重庆银行(601963 SH)公布,截至本公告日,本次增持计划

2023-07-28

广东防风应急响应降为Ⅳ级 仍需防范次生灾害

原标题:广东防风应急响应降为Ⅳ级仍需防范次生灾害中新网广州7月28日

2023-07-28

四川一个“偏僻”的古镇,不需要门票,曾繁华一时,如今归于平静

中国是一个迅速发展的国家,无论是政治军事经济,现在的中国和过去的时

2023-07-28

7月28日山东地区加氢苯市场价格上调

山东地区加氢苯市场主流报价区间在7200~7250元 吨,均价7225元 吨,较

2023-07-28

2023杭州台风滨江区应急救援电话一览

2023杭州台风滨江区应急救援电话区县队伍名称(民政注册名称)队伍队长姓

2023-07-28

2023年生态保护龙头概念股有哪些(7月28日)

2023年生态保护龙头概念股有哪些(7月28日),2023年生态保护龙头概念股

2023-07-28

资讯

胡松辉:澳门特区正在努力争取承办CBA赛事

昨晚,第六届粤澳杯第二回合的比赛在恩平体育中心落下帷幕,广东队以89∶77赢下比赛。两回合比赛,广东队以177∶160的优势获得本届比赛的冠

2022-07-08     
北京推出14条秋游文化线路

金秋时节,北京市文化和旅游局以赏银杏品文化为主题,推出14条“叶落的季节——漫步北京赏银杏品文化主题线路”,邀市民和游客以步行、骑行

2021-10-27     
基因编辑发力 培育高质量人源化供体猪

此次人体试验,仅仅验证了基因编辑猪克服异种器官移植的超急性排斥反应,还需解决延迟性排斥反应、消耗性血栓等问题。但通过这次试验,能更

2021-10-27     
中国经济高质量发展步伐稳健 长期向好基本面未变

在全球疫情走势和经济走势趋于复杂的背景下,中国经济巨轮将驶向何方,举世关注。2020年10月26日至29日,党的十九届五中全会在京举行,明确

2021-10-27     
南美解放者杯决赛允许近4.5万观众入场

南美洲足联主席多明格斯25日与今年解放者杯决赛对阵的两支俱乐部负责人会晤,宣布决赛现场观众人数增加到球场容量的75%,即近4 5万人。今年

2021-10-27     
22年从警生涯 面对荣誉他说不要给我报功

9月24日,时任安徽省安庆市公安局迎江分局刑警大队大要案中队中队长周磊因在工作中激烈搏斗引发心源性猝死,倒在了工作岗位上,经医院抢救

2021-10-27