本文为笔者阅读《嵌入式C精华》的摘录,推荐一下,不错的书

一、C/C++语言 struct深层探索

1.自然对界

  struct是一种复合数据类型,其构成元素既可以是基本数据类型(如 int、long、float等)的变量,也可以是一些复合数据类型(如 array、stuct、union等)的数据单元。对于结构体,编译器会自动进行成员变量的对齐,以提高运算效率。

  缺少情况下,编译器为结构体的每个成员按其自然对界(natural alignment:即默认对齐方式,是指按结构体成员中 size最大的成员对齐)条件分配空间。各个成员按照它们被声明的顺序在内存中的顺序存储,第一个成员的地址和整个结构的地址相同。

2.指定对界

  一般地,可以能过下面的方法来改变缺少的对界条件

  • 使用伪指令 #prama pack(n),编译器将按照 n个字节对齐
  • 使用伪指令 #prama pack(),取消自定义字节对齐方式

  注意:如果 #pragma pack(n)中指定的 n大于结构体中最大成员的 size,则其不起作用,结构体仍然按照 size最大的成员进行对界。

  例如:

  1. #pragma pack (n)
  2. struct naturalalign
  3. {
  4.   char a;
  5.   int b;
  6.   char c;
  7. };

  当n为4、8、16时,其对齐方式均一样,sizeof(naturalalign)的结果都等于12。而当n为2 时,其发挥了作用,使得sizeof(naturalalign)的结果为6。

3.C 和 C++间 struct 的深层区别

  在C++语言中struct具有了“类” 的功能,其与关键字class的区别在于struct中成员变量 和函数的默认访问权限为public,而class的为private。

  C++中的struct保持了对C中struct的全面兼容(这符合C++的初衷——“a better c”),
  因而,下面的操作是合法的:

  1. //定义struct
  2. struct structA
  3. {
  4. char a;
  5. char b;
  6. int c;
  7. };
  8. structA a = {'a' , 'a' ,}; // 定义时直接赋初值

  即struct可以在定义的时候直接以{ }对其成员变量赋初值,而class则不能

4.struct 编程注意事项

  在 C 语言中,当结构体中存在指针型成员时,一定要注意在采用赋值语句时是否将 2 个实例中的指针型成员指向了同一片内存。

  在C++语言中,当结构体中存在指针型成员时,我们需要重写struct的拷贝构造函数并进行“=” 操作符重载。

二、C++中 extern "C"含义深层探索 

1.引言

  C++语言的创建初衷是“a better C”,但是这并不意味着C++中类似C语言的全局变量和函数所采用的编译和连接方式与C语言完全相同。作为一种欲与C兼容的语言,C++保留了一部分过程式语言的特点(被世人称为“不彻底地面向对象”),因而它可以定义不属于任何类的全局变量和函数。但是,C++毕竟是一种面向对象的程序设计语言,为了支持函数的重载,C++对全局函数的处理方式与C有明显的不同。

2.从标准头文件说起

  某企业曾经给出如下的一道面试题:
  面试题:为什么标准头文件都有类似以下的结构?

  1. #ifndef __INCvxWorksh
  2. #define __INCvxWorksh
  3. #ifdef __cplusplus
  4. extern "C" {
  5. #endif
  6. /*...*/
  7. #ifdef __cplusplus
  8. }
  9. #endif
  10. #endif /* __INCvxWorksh */

  分析 
  显然,头文件中的编译宏“#ifndef __INCvxWorksh、#define __INCvxWorksh、#endif” 的作用是防止该头文件被重复引用。
  那么下面代码的作用又是什么呢?

  1. #ifdef __cplusplus
  2. extern "C" {
  3. #endif
  4. #ifdef __cplusplus
  5. }
  6. #endif

3.深层揭密 extern "C"

  extern "C" 包含双重含义,从字面上即可得到:首先,被它修饰的目标是“extern”的;其次,被它修饰的目标是“C”的。让我们来详细解读这两重含义。
  (1)被extern "C"限定的函数或变量是extern类型的;
  extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。记住,下列语句:
  extern int a;
  仅仅是一个变量的声明,其并不是在定义变量a,并未为a分配内存空间。变量a在所有模块中作为一种全局变量只能被定义一次,否则会出现连接错误。
  通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数。
  与extern对应的关键字是static,被它修饰的全局变量和函数只能在本模块中使用。因此,一个函数或变量只可能被本模块使用时,其不可能被extern “C”修饰。
  (2)被extern "C"修饰的变量和函数是按照C语言方式编译和连接的。

未加 extern “C”声明时的编译方式

  首先看看C++中对类似C的函数是怎样编译的。
  作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:
  void foo( int x, int y );
  该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。_foo_int_int这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。
  同样地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以"."来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。

未加 extern "C"声明时的连接方式

  假设在C++中,模块A的头文件如下:

  1. // 模块A头文件 moduleA.h
  2. #ifndef MODULE_A_H
  3. #define MODULE_A_H
  4. int foo( int x, int y );
  5. #endif

  在模块B中引用该函数:

  1. // 模块B实现文件 moduleB.cpp
  2. #include "moduleA.h"
  3. foo(,);

  实际上,在连接阶段,连接器会从模块A生成的目标文件moduleA.obj中寻找_foo_int_int这样的符号!

加 extern "C"声明后的编译和连接方式

  加extern "C"声明后,模块A的头文件变为:

  1. // 模块A头文件 moduleA.h
  2. #ifndef MODULE_A_H
  3. #define MODULE_A_H
  4. extern "C" int foo( int x, int y );
  5. #endif

  在模块B的实现文件中仍然调用foo( 2,3 ),其结果是:

  (1)模块A编译生成foo的目标代码时,没有对其名字进行特殊处理,采用了C语言的方式;
  (2)连接器在为模块B的目标代码寻找foo(2,3)调用时,寻找的是未经修改的符号名_foo。
  如果在模块A中函数声明了foo为extern "C"类型,而模块B中包含的是extern int foo( int x, int y ) ,则模块B找不到模块A中的函数;反之亦然。
  

  所以,可以用一句话概括extern “C”这个声明的真实目的(任何语言中的任何语法特性的诞生都不是随意而为的,来源于真实世界的需求驱动。我们在思考问题时,不能只停留在这个语言是怎么做的,还要问一问它为什么要这么做,动机是什么,这样我们可以更深入地理解许多问题)

实现 C++与C 及其它语言的混合编程。

  明白了C++中extern "C"的设立动机,我们下面来具体分析extern "C"通常的使用技巧。

4.extern "C"的惯用法

  (1)在C++中引用C语言中的函数和变量,在包含C语言头文件(假设为cExample.h)时,需进行下列处理:

  1. extern "C"
  2. {
  3. #include "cExample.h"
  4. }

  而在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern "C"声明,在.c文件中包含了extern "C"时会出现编译语法错误。 
  编写一个C++引用C函数例子工程中包含的三个文件的源代码如下:

  1. /* c语言头文件:cExample.h */
  2. #ifndef C_EXAMPLE_H
  3. #define C_EXAMPLE_H
  4. extern int add(int x,int y);
  5. #endif
  6. /* c语言实现文件:cExample.c */
  7. #include "cExample.h"
  8. int add( int x, int y )
  9. {
  10. return x + y;
  11. }
  12.  
  13. // c++实现文件,调用add:cppFile.cpp
  14. extern "C"
  15. {
  16. #include "cExample.h"
  17. }
  18. int main(int argc, char* argv[])
  19. {
  20. add(,);
  21. return ;
  22. }

  如果C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern "C" { }。

  (2)在C中引用C++语言中的函数和变量时,C++的头文件需添加extern "C",但是在C语言中不能直接引用声明了extern "C"的该头文件,应该仅将C文件中将C++中定义的extern "C"函数声明为extern类型。
  编写一个C引用C++函数例子工程中包含的三个文件的源代码如下:

  1. //C++头文件 cppExample.h
  2. #ifndef CPP_EXAMPLE_H
  3. #define CPP_EXAMPLE_H
  4. extern "C" int add( int x, int y );
  5. #endif
  6.  
  7. //C++实现文件 cppExample.cpp
  8. #include "cppExample.h"
  9. int add( int x, int y )
  10. {
  11. return x + y;
  12. }
  13.  
  14. /* C实现文件 cFile.c
  15. /* 这样会编译出错:#include "cExample.h" */
  16. extern int add( int x, int y );
  17. int main( int argc, char* argv[] )
  18. {
  19. add( , );
  20. return ;
  21. }

  如果深入理解了上面所阐述的extern "C"在编译和连接阶段发挥的作用,就能真正理解本节所阐述的从C++引用C函数和C引用C++函数的惯用法。

  对示例代码,需要特别留意各个细节。

 三、C 语言高效编程的几招 

第 1 招:以空间换时间

  例如:字符串的赋值。

  方法 A,通常的办法:

  1. #define LEN 32
  2. char string1 [LEN];
  3. memset (string1,,LEN);
  4. strcpy (string1,"This is an example!!")

  方法 B:

  1. const char string2[LEN]="This is an example!"
  2. char*cp;
  3. cp=string2;

  从上面的例子可以看出,A 和 B 的效率是不能比的。在同样的存储空间下,B 直接使用指针就可以操作了,而 A 需要调用两个字符函数才能完成。

  B 的缺点在于灵活性没有A 好。在需要频繁更改一个字符串内容的时候,A 具有更好的灵活性;如果采用方法 B,则需要预存许多字符串,虽然占用了 大量的内存,但是获得了程序执行的高效率。

  如果系统的实时性要求很高,内存还有一些,那么推荐使用该招数。

该招数的边招:使用宏函数而不是函数。

  函数和宏函数的区别就在于,宏函数占用了大量的空间,而函数占用了时间。
  大家要知道的是,函数调用是要使用系统的栈来保存数据的,如果编译器里有栈检查选项,一般在函数的头会嵌入一些汇编语句对当前栈进行检查;同时,CPU 也要在函数调用时保存和恢复当前的现场,进行压栈和弹栈操作,所以,函数调用需要一些 CPU 时间。
  而宏函数不存在这个问题。宏函数仅仅作为预先写好的代码嵌入到当前程序,不会产生函数调用,所以仅仅是占用了空间,在频繁调用同一个宏函数的时候,该现象尤其突出。

第 2 招:数学方法解决问题

  数学是计算机之母,没有数学的依据和基础,就没有计算机的发展,所以在编写程序的时候,采用一些数学方法会对程序的执行效率有数量级的提高。
  举例如下,求 1~100 的和。

  方法 E

  1. int I,j;
  2. for (I=; I<=; I++)
  3. {
  4. j+=I;
  5. }

  方法 F

  1. int I;
  2. I=(*(+))/

  方法 E 循环了 100 次才解决问题,也就是说最少用了 100 个赋值、100 个判断、200 个加法(I 和 j) ;而方法 F 仅仅用了 1 个加法、1 个乘法、1 次除法。效果自然不言而喻。

第 3 招:使用位操作

  使用位操作,减少除法和取模的运算。
  在计算机程序中,数据的位是可以操作的最小数据单位,理论上可以用“位运算”来完成所有的运算和操作。一般的位操作是用来控制硬件的,或者做数据变换使用,但是,灵活的位操作可以有效地提高程序运行的效率。
  举例如下:

  方法 G

  1. int I,J;
  2. I=/;
  3. J=%;

  方法 H

  1. int I,J;
  2. I=>>;
  3. J=-(>><<);

  在字面上好象 H 比G 麻烦了好多,但是,仔细查看产生的汇编代码就会明白,方法 G 调用了基本的取模函数和除法函数,既有函数调用,还有很多汇编代码和寄存器参与运算;而方法 H 则仅仅是几句相关的汇编,代码更简洁、效率更高。当然,由于编译器的不同,可能效率的差距不大,但是,以目前的 MS C,ARM C 来看,效率的差距还是不小。相关汇编代码就不列举了。

  运用这招需要注意的是,因为 CPU 的不同而产生的问题。比如说,在 PC 上用这招编写的程序,并在 PC 上调试通过,在移植到一个 16 位机平台上的时候,可能会产生代码隐患。所以只有在一定技术进阶的基础下才可以使用这招。

第 4 招:汇编嵌入

  在熟悉汇编语言的人眼里,C 语言编写的程序都是垃圾”。这种说法虽然偏激了一些,但是却有它的道理。汇编语言是效率最高的计算机语言,但是,不可能靠着它来写一个操作系统吧?所以,为了获得程序的高效率,我们只好采用变通的方法:嵌入汇编、混合编程。

  举例如下,将数组一赋值给数组二,要求每一个字节都相符。char string1[1024], string2[1024];

  方法 I

  1. int I;
  2. for (I=; I<; I++)
  3. *(string2+I)=*(string1+I)

  方法 J

  1. #int I;
  2. for(I=; I<; I++)
  3. *(string2+I)=*(string1+I);
  4. #else
  5. #ifdef_ARM_
  6. _asm
  7. {
  8. MOV R0,string1
  9. MOV R1,string2
  10. MOV R2,#
  11. loop:
  12. LDMIA R0!,[R3-R11]
  13. STMIA R1!,[R3-R11]
  14. ADD R2,R2,#
  15. CMP R2, #
  16. BNE loop
  17. }
  18. #endif

  方法 I 是最常见的方法,使用了 1024 次循环;方法 J 则根据平台不同做了区分,在 ARM 平台下,用嵌入汇编仅用 128次循环就完成了同样的操作。这里有朋友会说,为什么不用标准的内存拷贝函数呢?这是因为在源数据里可能含有数据为 0 的字节,这样的话,标准库函数会提前结束而不会完成我们要求的操作。这个例程典型应用于 LCD 数据的拷贝过程。根据不同的 CPU,熟练使用相应的嵌入汇编,可以大大提高程序执行的效率。

  虽然是必杀技,但是如果轻易使用会付出惨重的代价。这是因为,使用了嵌入汇编,便限制了程序的可移植性,使程序在不同平台移植的过程中,卧虎藏龙、险象环生!同时该招数也与现代软件工程的思想相违背,只有在迫不得已的情况下才可以采用。切记。

  摘自《单片机与嵌入式系统应用》

【C/C++】struct探索·extern "C"含义探索 ·C++与C的混合编程·C 语言高效编程的几招的更多相关文章

  1. [收藏转贴]struct探索·extern "C"含义探索 ·C++与C的混合编程·C 语言高效编程的几招

    一.C/C++语言 struct深层探索 1.自然对界 struct是一种复合数据类型,其构成元素既可以是基本数据类型(如 int.long.float等)的变量,也可以是一些复合数据类型(如 arr ...

  2. C++中extern “C”含义深层探索

    C++中extern “C”含义深层探索 extern “C” 是一个双向都需要用到的语法表示,就是说在cpp引用c头文件,或者c引用cpp文件时都需要用到.但extern “C” 永远只能在cpp引 ...

  3. (转)C++中extern “C”含义深层探索

    (转)C++中extern “C”含义深层探索 转自: http://www.cppblog.com/Macaulish/archive/2008/06/17/53689.html 1.引言 C++语 ...

  4. C++中extern “C”含义深层探索(在原作的基础上修改) .

    1. 引言 C++ 语言的创建初衷是“a better C” ,但是这并不意味着C++ 中类似C 语言的全局变量和函数所采用的编译和连接方式与C 语言完全相同.作为一种欲与C 兼容的语言,C++ 保留 ...

  5. C++ - extern “C”含义深层探索

    C和C++函数的相互引用原文地址:http://blog.csdn.net/wfwd/archive/2006/05/30/763734.aspx=========================== ...

  6. C++中extern “C”含义及extern、static关键字浅析

    https://blog.csdn.net/bzhxuexi/article/details/31782445 1.引言 C++语言的创建初衷是“a better C”,但是这并不意味着C++中类似C ...

  7. extern "C" 含义

    extern "C" 被 extern 限定的函数或变量是 extern 类型的 被 extern "C" 修饰的变量和函数是按照 C 语言方式编译和链接的 e ...

  8. C语言socket编程----struct sockaddr 和struct sockaddr_in介绍和初始化

    sockaddr结构体 struct  sockaddr{ sa_family_t  sa_family; //地址族,最常用的是"AF_INET"(IPV4)和"AF_ ...

  9. 2、网络并发编程--套接字编程、黏包问题、struct模块、制作简易报头、上传文件数据

    昨日内容回顾 面向对象复习(json序列化类) 对象.类.父类的概念 三大特性:封装 继承 多态 双下开头的方法(达到某个条件自动触发) __init__:对象实例化自动触发 __str__:对象执行 ...

随机推荐

  1. 像python一样运行js的__main__

    在测试时,使用单文件进行简单测试既简洁又清晰.js其实也是可以做到的 if (require && require.main == module) { console.log(&quo ...

  2. StompClient的包装类

    为了简化MQ调用,写了个StompClient的包装类,可以供需要的参考: unit FStompClient; interface uses SysUtils, Windows, Messages, ...

  3. 关于 Dropout 防止过拟合的问题

    关于 Dropout 可以防止过拟合,出处:深度学习领域大神 Hinton,在2012年文献:<Improving neural networks by preventing co-adapta ...

  4. MYSQL-innodb性能优化几个点

    MYSQL-innodb性能优化几个点 数据库常用参数 MYSQL数据库的参数配置一般在my.ini配置(部分参数也可以用set  global 参数名=值 做临时调整,重启后失效),配置完后需要重启 ...

  5. java http下载文件/上传文件保存

    private boolean downloadFile(String httpUrl, String savePath) { int byteread = 0; try { URL url = ne ...

  6. WCF Service中HttpContext.Current为null的解决办法

    1. 在hosting WCF的web.config中加入: <system.serviceModel> <serviceHostingEnvironment aspNetCompa ...

  7. [转]在Linux CentOS 6.6上安装Python 2.7.9

    在Linux CentOS 6.6上安装Python 2.7.9 查看python安装版本 python -V yum中最新的也是Python 2.6.6,所以只能下载Python 2.7.9的源代码 ...

  8. SourceTree 代码库管理工具

    1.SourceTree 简介 SourceTree 是拥有可视化界面的项目版本控制软件,适用于 git 项目管理,window.mac 均可用. 官网下载地址 SourceTree 其它下载地址 S ...

  9. php数组添加元素的方法

    PHP数组添加一个元素的方式: push(),  arr[], Php代码   $arr = array(); array_push($arr, el1, el2 ... eln); 但其实有一种更直 ...

  10. 在JSP中如何使用JavaBean

    在JSP中使用JavaBean以后,可以实现HTML代码和Java代码的分离,是JSp更易于开发和维护.因此JavaBean成了JSP程序员必备的利器.虽然javaBean是java类,但是它也有自己 ...