嵌入式C惯用法

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/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值