嵌入式C惯用法

2023-10-27

1、cpp里的c代码按照c的方式来编译和调用

时常在cpp的代码之中看到这样的代码:

#ifdef __cplusplus 
extern "C" { 
#endif

//一段代码

#ifdef __cplusplus 

#endif 
  这样的代码到底是什么意思呢?首先,__cplusplus是cpp中的自定义宏,那么定义了这个宏的话表示这是一段cpp的代码,也就是说,上面的代码的含义是:如果这是一段cpp的代码,那么加入extern "C"{和}处理其中的代码。

C++之父在设计C++之时,考虑到当时已经存在了大量的C代码,为了支持原来的C代码和已经写好C库,需要在C++中尽可能的支持C,而extern "C"就是其中的一个策略。为了在C++代码中调用用C写成的库文件,就需要用extern "C"来告诉编译器:这是一个用C写成的库文件,请用C的方式来链接它们。

2、多使用移位代替乘除操作,效率高

大小端交换操作:


int32_t swapInt32(int32_t value)
{
     return ((value & 0x000000FF) << 24) |
               ((value & 0x0000FF00) << 8) |
               ((value & 0x00FF0000) >> 8) |
               ((value & 0xFF000000) >> 24) ;
}

判断2的幂:

static bool is_power_of_2(uint32_t x)
{
  return (x != 0) && ((x & (x - 1)) == 0);
}

 

3、头文件中的static inline函数

     inline 关键字实际上仅是建议内联并不强制内联,gcc中O0优化时是不内联的,即使是O2以上,如果该函数被作为函数指针赋值,那么他也不会内联,也必须产生函数实体,以获得该函数地址。经测试c文件中的仅inline函数即使Os优化也不内联,因为没有static,编译认他是全局的,因此像普通函数一样编译了,本c文件也一样通过 bl inline_func 这样的方式调用,不像网上别人说的,本c会内联,其他c文件则通过bl inline_func 方式。加入static 后则内联了。(Os优化等级测试)
       所以在头文件中用inline时务必加入static,否则当inline不内联时就和普通函数在头文件中定义一样,当多个c文件包含时就会重定义。所以加入static代码健壮性高,如果都内联了实际效果上是一样的。(gcc下验证过O0级别includes.h中仅定义inline的函数,编译失败,Os编译成功)
 

为什么要在头文件中定义函数呢?
虽然知道了头文件中用inline函数时要加入static,但是为什么要在头文件中定义函数呢?
一些简单的封装接口函数,如 open() { vfs_open() } 仅仅是为了封装一个接口,我们不希望耗费一次函数调用的时间,解决方法一是宏,但是作为接口,宏不够清晰。那选择inline,但是如果在c文件中写
main.c
inline void open(void)
{
    vfs_open();

头文件加声明,外部要使用则不会内联的,因为编译器有个原则,以c文件为单位进行逐个编译obj,每个c文件的编译是独立的,该c文件用到的外部函数都在编译时预留一个符号,只有等到所有obj生成后链接时才给这些符号地址(链接脚本决定地址),所以其他c文件编译时只会看到这个函数的声明而无法知道她的实体,就会像普通函数一样通过bl 一个函数地址,等链接的时候再填入该地址了,他做不到内联展开。
所以要内联则必须在每个用到它的c文件体现实体,那就只有在头文件了,所以会把这类希望全局使用又希望增加效率的函数实现在头文件中static inline。

static inline 的坏处
    因为inline 是C99才有的关键字,C89没有,有部分编译器不支持,或者部分支持,如支持__inline 或 __inline__等,所以我们一般会用一个宏定义inline 如:
#define INLINE    static inline
不支持inline时:
#define INLINE    static
    但是这样如果编译器不支持inline 即意味着之前 static inline的函数全部被修改为 static,在头文件中写static会有什么后果呢?
经过测试果然和我们想的一样,每个c文件包含了该头文件后全部都有了该函数副本。这无疑增大了很多代码量。比如在include.h
这样的大头文件,几乎每个c文件我们都会包含他,相当于每一C文件都会加入一个 static void func(void){...}  实体。如果是函数宏则不会有这种问题,函数宏是没有实际代码的,没调用他时代码不存在。这就是头文件中用static inline 函数的坏处。但是可以通过优化解决,经过测试,O0优化下在头文件中定义static 函数包含该头文件的三个c文件的确都有了该函数,但是在Os优化下则只有调用了该函数的C文件才有实体。这是由编译器对static函数的特性决定的。总之他的法则和我们想的一致,就是头文件仅仅是单纯的展开,而每个C独立编译,不会因为知道其他个C文件定义了该函数,这个c文件就把他当外部bl了。

 

4、UL类型

0UL--------无符号长整型0
1UL--------无符号长整型1
如果没有UL后缀,则系统默认为 int类型,即,有符号整形

5、结构体中的偏移量

#define offsetof(TYPE,MEMBER)   (size_t)&((TYPE*)0)->MEMBER

6、结构体字节对齐

字节对齐的作用不仅是便于cpu快速访问,同时合理的利用字节对齐可以有效地节省存储空间。

对齐跟数据在内存中的位置有关。如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。比如在32位cpu下,假设一个整型变量的地址为0x00000004,那它就是自然对齐的。

需要字节对齐的根本原因在于CPU访问数据的效率问题。假设上面整型变量的地址不是自然对齐,比如为0x00000002,则CPU如果取它的值的话需要访问两次内存,第一次取从0x00000002-0x00000003的一个short,第二次取从0x00000004-0x00000005的一个short然后组合得到所要的数据,如果变量在0x00000003地址上的话则要访问三次内存,第一次为char,第二次为short,第三次为char,然后组合得到整型数据。而如果变量在自然对齐位置上,则只要一次就可以取出数据。一些系统对对齐要求非常严格,比如sparc系统。

数组 :按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。 
联合 :按其包含的长度最大的数据类型对齐。 
结构体: 结构体中每个数据类型都要对齐。

我们可以按照自己设定的对齐大小来编译程序,GNU使用__attribute__选项来设置,比如我们想让刚才的结构按一字节对齐,我们可以这样定义结构体
  
  struct stu{
   char sex;
   int length;
   char name[10];
  }__attribute__ ((aligned (1))); 
  
  struct stu my_stu;
   
  
  则sizeof(my_stu)可以得到大小为15。否则不设置aligned的话得到的大小为20。

7、位段

一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。

位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。

如果结构体中含有位域(bit-field),那么VC中准则是:

  1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;

  2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;

  3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式(不同位域字段存放在不同的位域类型字节中),Dev-C++和GCC都采取压缩方式;

  系统会先为结构体成员按照对齐方式分配空间和填塞(padding),然后对变量进行位域操作。

8、部分函数实现

1.下面来看strcpy()原型写法: 字符串拷贝. 
char *strcpy(char *strDest, const char *strSrc)
{
assert((strDest!=NULL) && (strSrc !=NULL));
char *address = strDest; 
while( (*strDest++ = * strSrc++)·1 != '/0') 
NULL ; 
return address ; 
}

2.下面来看下memcpy函数的原型写法:内存拷贝

void *memcpy(void *dest, const void *source, size_t count)
{
assert((NULL != dest) && (NULL != source));

char *tmp_dest = (char *)dest;
char *tmp_source = (char *)source;
while(count --)//不对是否存在重叠区域进行判断
*tmp_dest ++ = *tmp_source ++;
return dest;
}
3.下面来看下memmove函数的原型写法:

void *memmove(void *dest, const void *source, size_t count)
{
assert((NULL != dest) && (NULL != source));
char *tmp_source, *tmp_dest;
tmp_source = (char *)source;
tmp_dest = (char *)dest;
if((dest + count<source) || (source + count) <dest))
{// 如果没有重叠区域
while(count--)
*tmp_dest++ = *tmp_source++;
}
else
{ //如果有重叠(反向拷贝)
tmp_source += count - 1;
tmp_dest += count - 1;
while(count--)
*--tmp_dest = *--tmp;
}
return dest;
}
单链表排序:

void link_order(STU *p_head)
{
    STU *pb, *pf, temp;
    pf = p_head;
    if(p_head == NULL) {//链表为空
        printf("needn't order.\n");
        return ;
    }
    if(p_head->next == NULL) {//链表有1个节点
        printf("only one print, needn't order.\n");
        return ;
    }
    while(pf->next != NULL) {//以pf指向的节点为基准节点
        pb = pf->next;//pb从基准点的下一个节点开始
        while(pb != NULL) {
            if(pf->num > pb->num) {
                temp = *pf;
                *pf = *pb;
                *pb = temp;
                temp.next = pf->next;
                pf->next = pb->next;
                pb->next = temp.next;
            }
            pb = pb->next;
        }
        pf = pf->next;
    }
    return ;
}

9、typeof用法

struct list_head
{
    struct list_head *prev,*next;
}

struct person
{
    int age;
    char name[20];
    list_head list;
}

struct person *pperson;


在看双向链表的标准实现里,有一个写法,list_entry:container_of(ptr,type,member),其中ptr=pperson,type=struct person,member=list名字替换而已
 const typeof( ((type *)0)->member ) *__mptr = (ptr);            //定义了一个指针变量*__mptr
 (type *)( (char *)__mptr - offsetof(type,member) );})           //通过结构体成员变量的指针获取指向整个结构体的指针
 
 
typeof不是C语言本身的关键词或运算符(sizeof是C标准定义的运算符),它是GCC的一个扩展,作用正如其字面意思,用某种已有东西(变量、函数等)的
类型去定义新的变量类型。typeof()中可以是任何有类型的东西,变量就是其本身的类型,函数是它返回值的类型。typeof一般用于声明变量,如:typeof(a) var;
typeof()是在编译时处理的,故其中的表达式在运行时是不会被执行的。typeof还有一些局限性,其中的变量是不能包含存储类说明符的,如static、extern这类都是不行的。
 

 

 

参考链接:https://www.cnblogs.com/yuemw/p/7908413.html

https://blog.csdn.net/kuai0705/article/details/20841133

https://blog.csdn.net/huanghui167/article/details/41346663

https://blog.csdn.net/lanzhihui_10086/article/details/44353381

https://www.cnblogs.com/bigrabbit/archive/2012/09/20/2695543.html

https://blog.csdn.net/yahohi/article/details/7927806

https://gaomf.cn/2017/10/07/C_typeof/

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

嵌入式C惯用法 的相关文章

  • buildroot教程

    什么是buildroot Buildroot是Linux平台上一个开源的嵌入式Linux系统自动构建框架 0 下载buildroot Buildroot版本每2个月 2月 5月 8月和11月发布一次 版本号的格式为YYYY MM 例如201
  • 【翻译】全新16英寸MacBook Pro评测:开发人员的梦想成真

    要问现在适合开发者用的笔记本 市面上还是有很多选择的 比如Dell的XPS系列 外星人系列 游戏也是杠杠滴 联想拯救者系列 还有形形色色的高配机型 价格也从几千到几万不等 但是 笔吧评测室的猪哥说过 从一万元开始 就已经难以用定位的方式来给
  • Mycat windows安装

    id iframe1033655 0 src http pos baidu com jcrm rtbid 2097325 rdid 9223372032564562618 dc 2 di 1033655 dri 0 dis 0 dai 1
  • 报错:JSONException: illegal identifier : \pos 1, line 1, column 2 或not close json text, token : error

    报错 JSONException illegal identifier pos 1 line 1 column 2 或JSONException not close json text token error 简述问题 处理 结果 简述问题
  • spring IOC控制反转及IOC实现的三种方式和bean标签使用

    spring IOC控制反转及IOC实现的三种方式和bean标签使用 IOC 控制反转 简单的说是指对象的创建不再使用new 而是由spring框架创建 当服务器开始运行时 读取spring的主配置文件 经过spring框架从主配置文件中识
  • 黄勇-flask教程-学习笔记

    课时4 虚拟环境 pip install virtualenv 安装虚拟环境 virtualenv venv 创建虚拟环境 venv Scripts activate 激活虚拟环境 课时10 url反转 url for url for引用视
  • linux命令如何查看dns,linux查看dns命令

    linux下我们要查看服务器的DNS信息可以通过命令来实现 下面由学习啦小编为大家整理了linux查看dns命令的相关知识 希望对大家有帮助 linux查看dns命令1 查看 etc resolv con文件 root localhost
  • 索引原理学习

    一 介绍 1 什么是索引 一般的应用系统 读写比例在10 1左右 而且插入操作和一般的更新操作很少出现性能问题 在生产环境中 我们遇到最多的 也是最容易出问题的 还是一些复杂的查询操作 因此对查询语句的优化显然是重中之重 说起加速查询 就不
  • linux安装jdk tar包

    1 java删除openJDK rpm qa grep java rpm e nodeps 查出来的java软件 例 rpm e nodeps java 1 8 0 openjdk headless 1 8 0 101 3 b13 el7

随机推荐

  • Elasticsearch好用查询插件分享

    以前我常用的ES查询工具是Head 作为插件形式在浏览器中运行 挺方便的 后来发现head不太好用 比如在数据浏览的时候 不小心就点击了两个索引 背景色设置的还不够明显 比较容易看错数据的 于是想找个更好用的工具 以前用过cerebro 觉
  • Linux内核学习(二):Bootloader

    Linux内核学习 二 UBOOT 在上一篇的文章中通过图片介绍了linux镜像的生成与加载 引出了一个东西叫uboot 是这个玩意一手把生成的内核镜像加载进去的 于是在进一步之前 我们得看看学习一下什么是Uboot 以及其工作流程和逻辑
  • com.google.common.base.Preconditions

    前提条件 Guava提供了许多前置条件检查实用程序 我们强烈建议您静态导入这些内容 每种方法都有三种变体 没有额外的争论 抛出任何异常都没有错误消息 一个额外的Object论点 抛出任何异常并显示错误消息 object toString 一
  • 【Linux】冯诺依曼体系结构思想

    冯诺依曼体系结构 冯诺依曼体系结构 冯诺依曼体系结构的五大部分 冯诺依曼体系结构的运行过程 存储器中的木桶效应 扩展 计算机存储设备金字塔 实例 qq聊天数据传输过程 小结 博客主页 小智 x0 0x 欢迎关注 点赞 收藏 留言 系列专栏
  • UnityVR--组件10--UGUI简单介绍

    目录 前言 UI基础组件 1 Canvas 2 EventSystem 3 Image 4 Text TextMeshPro InputField 5 Button控件 其他 前言 UGUI是Unity推出的新的UI系统 它与Unity引擎
  • 2023年第47届(第二届)浙江技能大赛网络安全项目 (世赛省选拔赛)C模块任务书

    2023年第47届 第二届 浙江技能大赛网络安全项目 世赛省选拔赛 C模块任务书 模块C 夺旗挑战赛 1竞项赛目简介 1 1 介绍 1 2环境和目标 1 2 1 CTF 架构 1 2 2 挑战 1 3 评分方案 1 4 工作流程 2竞赛项目
  • Unity接入GooglePlay服务

    请大家关注我的微博 NormanLin BadPixel坏像素 前置条件 Google开发者账号 需要支持Visa的信用卡 java与Android开发环境的搭建 Unity上连接AndroidSDK与Java jdk AndroidSDK
  • 毕业设计-基于机器学习的双目测距系统-OpenCV

    目录 前言 课题背景和意义 实现技术思路 一 系统环境要求与流程图 二 摄像机模型和标定 四 立体匹配与测距 五 测距系统实验结果 实现效果图样例 最后 前言 大四是整个大学期间最忙碌的时光 一边要忙着备考或实习为毕业后面临的就业升学做准备
  • 由动态库文件dll生成lib库文件

    本文基于OpenBlas的编译和安装 来说明怎样从一个dll文件生成lib库文件 參考OpenBlas的说明 Howto generate import library for MingW 和MinGW的说明HOWTO Create an
  • 揭秘前端文件上传原理(二)

    上一篇文章讲到了以Form表单 将文件数据编码为特定的类型 来作为前端文件上传的载体 这一篇再来看看 如果不使用Form表单 不以FormData去提交数据 我们又将如何上传文件到云端呢 Form表单的意义 首先来想一想 Form表单对文件
  • 详解 七大经典排序算法

    文章目录 概念 代码 一 插入排序 直接插入排序 希尔排序 二 选择排序 选择排序 堆排序 三 交换排序 冒泡排序 快速排序 四 归并排序 归并排序递归 归并排序非递归 法一 法二 五 非比较排序 计数排序 排序算法总结 复杂度和稳定性 效
  • mysql故障记录以及binlog2sql学习使用

    mysql两次故障记录 centos7 4和7 5 一 故障描述 故障一 mysql主库的vip漂移到了备库 20分钟后后人工切换了回来 由于不是主主同步模式 所以主库缺失了这写入备库的20分钟的数据 故障二 有人员误删生产库中某个表的几百
  • OAuth 简介

    OAuth是一个在不提供用户名和密码的情况下 授权第三方应用访问Web资源的安全协议 常用的应用 OAuth 的场景 一般是某个网站想要获取一个用户在第三方网站中的某些资源和服务 比如在人人网上 想要导入用户MSN里的好友 在没有OAuth
  • lua 的 table表 大小、元素个数 #操作 的体会【结论是错误的, 此后再更新】

    有个体会 lua table 的 操作 是针对 table insert table remove 这一对操作的 操作数维护 每次调用 table insert 都会是 操作值增加 这是我自己的表达 即使 用 table 取得表的 返回值
  • requests上传和flask接收OpenCV的图片数据

    方式一 从本地读取到图片或帧 上传到flask服务器 客户端发送 def image post data type code type code area id area id 以文件的格式上传 节省传输时间 file file file
  • 腾讯云技术大牛教你,MySQL内核深度优化

    作者介绍 简怀兵 腾讯云数据库高级工程师 负责腾讯云CDB内核及基础设施建设 先后供职于Thomson Reuters和YY等公司 PTimeDB作者 曾获一项发明专利 从事MySQL内核开发工作8年 具有丰富的优化经验 在分布式存储等领域
  • main(int argc, char **argv)中argc和argv的具体含义,以及操作系统如何处理它们

    main int argc char argv 中argc和argv的具体含义 以及操作系统如何处理它们 请高手详细解释一下 谢谢 1 argc 参数的个数 argv 参数的字符串形式的数组 2 C C code int main int
  • 解决display:none

    selenium 解决页面元素display none的方法 在UI自动化测试中 有时候会遇到页面元素无法定位的问题 包括xpath等方法都无法定位 是因为前端元素被设置为不可见导致 这篇博客 介绍下如何通过JavaScript修改页面元素
  • 2019CISCN华中赛区分区赛部分wp

    pwn1 64位程序 只开启了NX 栈不可执行 保护 试着运行发现是一个菜单题 选项二 三没用 拖到IDA中查看 发现在encrypt选项中存在gets造成的栈溢出漏洞 不过输进去的字符串被分段异或了 我们可以先进行异或一下 然后在输入程序
  • 嵌入式C惯用法

    1 cpp里的c代码按照c的方式来编译和调用 时常在cpp的代码之中看到这样的代码 ifdef cplusplus extern C endif 一段代码 ifdef cplusplus endif 这样的代码到底是什么意思呢 首先 cpl