【ARM 嵌入式 C 入门及渐进 3 -- GCC __attribute__ 使用】
文章目录
- 1.1 __attribute__((weak))
- 1.1.1 弱符号的声明
- 1.1.2 代码演示
- 1.1.3 编译与输出
- 1.2 extern "C"
- 1.2.1 补充介绍
- 1.2.2 作用场景
- 1.3 `__attribute__((used))`
- 1.4 `__read_mostly`
- 1.5 likely/unlikely
1.1 attribute((weak))
弱符号,这涉及到编译中符号的概念。在Linux开发环境中,有强符号和弱符号,符号简单来说就是函数、变量的名字,对于全局(非局部、非static)的函数和变量,能不能重名是有一定规矩的,强、弱符号就是针对这些全局函数和变量来说的。
符号类型 | 对象 |
---|---|
强 | 函数名,赋初值的全局变量 |
弱 | 未初始化的全局变量 |
当代码中同时存在多个强或弱的全局变量时,要遵守如下规则:
- 强符号只能定义一次,否则编译错误
- 强弱符号同时存在,以强符号为准
- 没有强符号,则从多个弱符号中任选一个,用
–fno-common
编译选项可以在这种情况下打出 warning。
1.1.1 弱符号的声明
弱符号的声明有两种方式
第一种,用 __attribute__((weak))
修饰,例如:
void __attribute__((weak)) func(void);
extern int __attribute__((weak)) var; //直接声明为弱函数
第二种,用 #pragma weak
标记,例如:
#pragma weak func
1.1.2 代码演示
main.c 这个文件中 main
函数调用了 2 个 声明为弱符号的函数,它们是 weak0
和weak1
:
#include <stdio.h>void __attribute__((weak)) weak0(void);
void __attribute__((weak)) weak1(void);int main(int argc, char **argv) {/* 尝试调用弱符号函数 weak0 */if (weak0) {weak0();} else {printf("weak0=%p\n", weak0);}/* 尝试调用弱符号函数 weak1 */if (weak1) {weak1();} else{printf("weak1=%p\n", weak1);}return 0;
}
weak.c 这个文件中定义了2个函数(它们还是weak0和weak1),并强制声明为弱符号:
#include <stdio.h>/* 标记 weak0 为弱符号 */
#pragma weak weak0
/* 标记 weak1 为弱符号 */
void __attribute__((weak)) weak1(void);static char *label = "weak";
void weak0(void) {printf("[%s]%s is called\n", label, __FUNCTION__);
}
void weak1(void) {printf("[%s]%s is called\n", label, __FUNCTION__);
}
stong.c 重复定义两个强函数:void weak0(void)
和 void weak1(void)
:
#include <stdio.h>static char *label = "strong";
void weak0(void) {printf("[%s]%s is called\n", label, __FUNCTION__);
}void weak1(void) {printf("[%s]%s is called\n", label, __FUNCTION__);
}
1.1.3 编译与输出
编译 gcc main.c strong.c weak.c
弱符号链接成功时,可以被正常调用。
当强符号定义出现时,弱符号定义不起作用,打印出来的是强函数的内容:
$ ./a.exe
[strong]weak0 is called
[strong]weak1 is called
1.2 extern “C”
extern “C” 的主要作用就是为了能够正确实现 C++ 代码调用其他 C 语言代码。加上 extern “C” 后,会指示编译器这部分代码按C语言(而不是C++)的方式进行编译。
由于 C++ 支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名。
而 C 语言并不支持函数重载,因此编译 C 语言代码的函数时不会带上函数的参数类型,一般只包括函数名。
1.2.1 补充介绍
由于C、C++ 编译器对函数的编译处理是不完全相同的,尤其对于 C++ 来说,支持函数的重载,编译后的函数一般是以函数名和形参类型来命名的。
例如,函数 void fun(int, int)
,C++ 编译后的可能是 _fun_int_int
(不同编译器可能不同,但都采用了类似的机制,用函数名和参数类型来命名编译后的函数名);而 C 语言没有类似的重载机制,一般是利用函数名来指明编译后的函数名的,对应上面的函数可能会是 _fun
这样的名字。
1.2.2 作用场景
这个功能主要用在下面的情况:
- C++代码调用C语言代码;
- 在C++的头文件中使用;
- 在多个人协同开发时,可能有的人比较擅长C语言,而有的人擅长 C++,这样的情况下也会有用到。
例如,如果模块 B 要引用模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样模块 B 中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但并不会报错;它会在链接阶段从模块A编译生成的目标代码中找到该函数。
extern 对应的关键字是 static,static 表明变量或者函数只能在本模块中使用,因此,被static修饰的变量或者函数不可能被 extern C 修饰。
1.3 __attribute__((used))
在普通的 C/C++ 程序中,有的时候为了调试,我们会特别地注释掉某个函数的调用。然而在编译时,编译器会发现,代码中实现了一个函数,但是最终却没有调用它,那么为什么还要写这个函数呢?于是会警告。
__attribute__((used))
,表示对于这个函数可能不会调用它、可能用不到它,编译器不用进行 warning 提示。
而在嵌入式中 中断函数都是由内部的中断处理机制通过中断向量做跳转调用的,不是开发人员 “显式” 去调用的,因此在一些规则检查比较严格的编译器上编译时,就会出现类似于上面的警告,为了视野干净我们就添加这个属性。
向编译器说明这段代码有用,即使在没有用到的情况下编译器也不会警告!告诉编译器避免被链接器因为未用过而被优化掉。
1.4 __read_mostly
在 arch/arm/kernel/process.c 中有如下定义:
unsigned logn stack_chk_guard __read_mostly
参考网上资料了解到 __read_mostly
修饰的变量放在定义为存放在 .data.read_mostly
段中。
#if defined(CONFIG_X86) || defined(CONFIG_SPARC64)
#define __read_mostly __attribute__((__section__(".data.read_mostly")))
#else
#define __read_mostly
#endif
Linux 内核被加载时,__read_mostly
修饰的数据将自动被存放到 Cache 中,以提高整个系统的执行效率。
如果所在的平台 没有 Cache,或者虽然有Cache,但并不提供存放数据的接口(也就是并不允许人工放置数据在Cache中),这样定义为 __read_mostly类型的数据将不能存放在Linux内核中,甚至也不能够被加载到系统内存去执行,将造成Linux 内核启动失败。
解决的方法有两种:
- 修改 include/asm/cache.h 中的
__ready_mostly
定义为:#define __read_mostly
- 修改 arch/xxx/kernel/vmlinux.S,将
.data.read_mostly
段的位置到实际内存空间中去,例如放置在.data
段之后等等。
1.5 likely/unlikely
在看 linux内核代码的时候,经常会看到 likely(x)
和 unlikely(x)
宏的使用。那这两个宏有什么作用呢?
这两个宏在内核中的定义如下:
# define likely(x) __builtin_expect(!!(x), 1)
# define unlikely(x) __builtin_expect(!!(x), 0)
可见这里使用了 gcc 的内建函数 __builtin_expect()
。
__builtin_expect (long exp, long c)
函数:
该函数用来引导 gcc 进行条件分支预测。在一条指令执行时,由于流水线的作用,CPU可以同时完成下一条指令的取指,这样可以提高CPU的利用率。在执行条件分支指令时,CPU也会预取下一条执行,但是如果条件分支的结果为跳转到了其他指令,那 CPU 预取的下一条指令就没用了,这样就降低了流水线的效率。
另外,跳转指令相对于顺序执行的指令会多消耗 CPU 时间,如果可以尽可能不执行跳转,也可以提高 CPU 性能。
使用 __builtin_expect (long exp, long c)
函数可以帮助 gcc 优化程序编译后的指令序列,使汇编指令尽可能的顺序执行,从而提高 CPU 预取指令的正确率和执行效率。
__builtin_expect(exp, c)
接受两个 long
型的参数,用来告诉 gcc:exp==c
的可能性比较大。
例如,__builtin_expect(exp, 1)
表示程序执行过程中,exp 取到 1 的可能性比较大。该函数的返回值为 exp 自身。
内核中 likely(x)
和 unlikely(x)
宏:
知道 __builtin_expect()
函数的作用之后,我们就知道内核中 likely(x)
和 unlikely(x)
宏的作用了,通过 likely(x)
和 unlikely(x)
宏定义,我们可以得出他们的作用:
- likely(x) 等价于 x,即
if (likely(x))
等价于if (x)
,但是它告诉 gcc,x 取 1 的可能性比较大; - unlikely(x) 等价于 x,即
if (unlikely(x))
等 价于if (x)
,但是它告诉 gcc,x 取 0 的可能性比较大。
推荐阅读:
https://www.cnblogs.com/xiangtingshen/p/10980055.html
https://www.cnblogs.com/tureno/articles/12236495.html
https://bbs.elecfans.com/jishu_1805890_1_1.html
https://blog.csdn.net/jasonchen_gbd/article/details/44968395
【ARM 嵌入式 C 入门及渐进 3 -- GCC __attribute__ 使用】相关推荐
- 【ARM 嵌入式 C 入门及渐进 6 -- Linux 内建函数 __builtin_】
文章目录 1.1 内建函数 1.1.1 内建函数 __builtin_return_address 1.1.2 内建函数 __builtin_frame_address 1.1.3 内建函数 __bu ...
- 【ARM 嵌入式 C 入门及渐进 4-- Linux 位图 bitmap】
文章目录 1.1 位图算法-bitmap 1.1.1 bitmap 使用场景示例 1.1.2 bitmap算法实现 1.1 位图算法-bitmap 位图算法,是指使用一个 bit 位来表示数据状态. ...
- 网络上所谓的《ARM嵌入式系统入门最好的文章》
一 首先说说ARM的发展 可以用一片大好来形容,翻开各个公司的网站,招聘里面嵌入式占据了大半工程师职位. 广义的嵌入式无非几种:传统的什么51.AVR.PIC称做嵌入式微控制器:ARM是嵌入式微处理器 ...
- arm嵌入式linux应用实例开发pdf,零点起步——ARM嵌入式Linux应用开发入门一书的源代码...
代码片段和文件信息 属性 大小 日期 时间 名称 ----------- --------- ---------- ----- ---- 文件 2 ...
- 嵌入式开发入门之经典 ARM开发板
嵌入式开发入门之经典 开始进入嵌入式世界,真是一头雾水,不知道如何入手!也不知道该如何学习,学习什么,最近从网上转载这篇文章,对我启发很大,对于初始进入嵌入式的人们很有帮组,好多嵌入式大侠都说这是入门 ...
- (嵌入式)ARM开发环境入门-----一个简单的LED灯闪烁的实现
ARM开发环境入门 一.一个简单的LED灯闪烁程序 1.1.工具 1.2.步骤 1.2.1.我们需要创建一个uVision Project 1.2.2.取名保存 1.2.3.这里选择我们需要的芯片类型 ...
- 嵌入式编程入门教程,学习设计嵌入式工程师
俗话说万事开头难(然后中间难,最后难?),刚开始的时候,你是否根本就不知如何开始,上网查资料被一堆堆新名词搞的找不到北,去看书也是找不到方向?又是arm,又是linux,又是uboot头都大了.不知道 ...
- ARM嵌入式的定义和开发工具介绍
综述:[e800专稿] ARM嵌入式简介 ARM(Advanced RISC Machines),既可认为是一个公司的名字,也可认为是对一类微处理器的统称. ARM是微处理器行业的一家知名企业,设计了 ...
- 嵌入式 Linux 入门(十、Linux 下的 C 编程)
嵌入式 Linux 入门第十课,聊聊 linux 下的 C 编程...... 矜辰所致 插一句,问题讨论群在文末的推广,以后大家提问可以在群中,即便我不在也能看到历史记录. 目录 前言 一.C 语言编 ...
- 嵌入式 Linux 入门(五、Shell 脚本编程上:认识 Shell 脚本)
大家好,是矜辰所致,嵌入式 Linux入 门第五课,本课开始简单学习一下 Shell 脚本编程. 目录 前言 一.Shell 脚本基础说明 1.1 什么是 Shell 脚本 1.2 Shell 脚本的 ...
最新文章
- SQL创建表语句文档
- swoole单台并发php,php swoole 并发多少?
- SAP配置webdynpro完全手册 .
- 图形化代码阅读工具——Scitools Understand
- ESDF建图库voxblox的安装编译过程
- 百度网盘自动备份php,服务器自动备份脚本上传至百度云存储
- 中国计算机类研究生学校排名,2018考研:计算机专业全球院校排名公布,上海交通大学竟排第一?...
- 2021-03-12 16个车辆信息检测数据集收集汇总
- 最常用三极管导通电路
- html自由变换图形,ps自由变换的快捷键是什么?
- 手机HTML5 audio 无法自动播放下一首
- AD(altium designer)15原理图与PCB设计教程(十)——信号完整性分析
- 每次运行项目都会出现这个reload script assemblies
- Mac中删除docker镜像
- 硬件学习_差模与共模
- 第七章 线程的活性故障--《java多线程编程实战指南-核心篇》
- C++核心准则边译边学-I.27 考虑使用指向实现的指针技术获得稳定的ABI
- 普通本科菜菜海淘无人搭理,苦心闭关修炼一个月,出关后成功拿下阿里,蚂蚁金服,美团三个大厂意向书
- OFBIZ分享:如何让OFBIZ使用中文界面
- 华为云找到 “成功”路径