内联函数在哪些场合使用

内联函数在哪些场合使用,第1张

1)简单的说,需要速度的时候。调用函数需要先将参数压栈,退出时还要清理堆栈里的局部变量,将返回值或其指针存入寄存器。至少在x86系列里是这么 *** 作的。这些动作都需要时间,所以为了避免这些 *** 作,可以将反复调用的函数作内联处理。比如,

for(int i = 0; i < 100000; i++)

objmethod(i);

如果这段代码的作用不是故意消磨时间,可以考虑内联处理。

2)如果对于某成员变量的进行封装,最好也采用内联函数。比如

class Employee

{

pubic:

Employee(int);

uint32 ID(){return _id;}

private:

uint32 _id;

}

显然这个ID又应该是不允许被未授权的机制进行改动的,所以需要对它private保护。但是一个雇员的ID是需要被外界知道,故需要一个public的接口供访问。这时,仅仅为了获取一个整数而大费周章的调用函数是不划算的。因此,可以将其内联。编译器会跳过堆栈处理,直接把某employee成员变量的便宜地址载入寄存器,然后寻址,把对于成员变量_id的值载入eax算作返回值。

3)注意:内联是个空间换时间的 *** 作。如果某段程序反复在不同位置内联,会大大增大exe文件的体积;还有,inline只是建议编译器去这么做。C++标准里并没有要求编译器必须实现;内联的虚函数也是存在的,但是需要慎用。

嵌入式的话首先把单片机玩顺了,从最简单的8位51单片机,到16位的MSP430,到32位的STM32这类都要比较熟悉。

同时也要熟悉单片机外围电路,这里用到模电数电知识。

可以利用单片机与各类模块(物联网常用蓝牙、WIFI、ZIGBEE等通信模块)搭配完成几个小项目这样掌握的更扎实一些。

接下来可以接触ARM,学LINUX,通过 *** 作系统来开发项目。

进程在运行过程中遇到逻辑错误, 比如除零, 空指针等等, 系统会触发一个软件中断

这个中断会以信号的方式通知进程, 这些信号的默认处理方式是结束进程

发生这种情况, 我们就认为进程崩溃了

进程崩溃后, 我们会希望知道它是为何崩溃的, 是哪个函数, 哪行代码引起的错误

另外, 在进程退出前, 我们还希望做一些善后处理, 比如把某些数据存入数据库, 等等

下面, 我会介绍一些技术来达成这两个目标

1 在core文件中查看堆栈信息

如果进程崩溃时, 我们能看到当时的堆栈信息, 就能很快定位到错误的代码

在 gcc 中加入 -g 选项, 可执行文件中便会包含调试信息 进程崩溃后, 会生成一个 core 文件

我们可以用 gdb 查看这个 core 文件, 从而知道进程崩溃时的环境

在调试阶段, core文件能给我们带来很多便利 但是在正式环境中, 它有很大的局限:

1 包含调试信息的可执行文件会很大 并且运行速度也会大幅降低

2 一个 core 文件常常很大, 如果进程频繁崩溃, 硬盘资源会变得很紧张

所以, 在正式环境中运行的程序, 不会包含调试信息

它的core文件的大小, 我们会把它设为0, 也就是不会输入core文件

在这个前提下, 我们如何得到进程的堆栈信息呢

2 动态获取线程的堆栈

c 语言提供了 backtrace 函数, 通过这个函数可以动态的获取当前线程的堆栈

要使用 backtrace 函数, 有两点要求:

1 程序使用的是 ELF 二进制格式

2 程序连接时使用了 -rdynamic 选项

-rdynamic可用来通知链接器将所有符号添加到动态符号表中, 这些信息比 -g 选项的信息要少得多

下面是将要用到的函数说明:

#include <execinfoh>

int backtrace(void buffer,int size);

用于获取当前线程的调用堆栈, 获取的信息将会被存放在buffer中, 它是一个指针列表。

参数 size 用来指定buffer中可以保存多少个void 元素。

函数返回值是实际获取的指针个数, 最大不超过size大小

注意: 某些编译器的优化选项对获取正确的调用堆栈有干扰,

另外内联函数没有堆栈框架; 删除框架指针也会导致无法正确解析堆栈内容;

char backtrace_symbols (void const buffer, int size)

把从backtrace函数获取的信息转化为一个字符串数组

参数buffer应该是从backtrace函数获取的指针数组,

size是该数组中的元素个数(backtrace的返回值) ;

函数返回值是一个指向字符串数组的指针, 它的大小同buffer相同

每个字符串包含了一个相对于buffer中对应元素的可打印信息

它包括函数名,函数的偏移地址, 和实际的返回地址

该函数的返回值是通过malloc函数申请的空间, 因此调用者必须使用free函数来释放指针

注意: 如果不能为字符串获取足够的空间, 函数的返回值将会为NULL

void backtrace_symbols_fd (void const buffer, int size, int fd)

与backtrace_symbols 函数具有相同的功能,

不同的是它不会给调用者返回字符串数组, 而是将结果写入文件描述符为fd的文件中,每个函数对应一行

3 捕捉信号

我们希望在进程崩溃时打印堆栈, 所以我们需要捕捉到相应的信号 方法很简单

#include <signalh>

void (signal(int signum,void( handler)(int)))(int);

或者: typedef void(sig_t) ( int );

sig_t signal(int signum,sig_t handler);

参数说明:

第一个参数signum指明了所要处理的信号类型,它可以是除了SIGKILL和SIGSTOP外的任何一种信号。

第二个参数handler描述了与信号关联的动作,它可以取以下三种值:

1 一个返回值为正数的函数的地址, 也就是我们的信号处理函数

这个函数应有如下形式的定义: int func(int sig); sig是传递给它的唯一参数。

执行了signal()调用后,进程只要接收到类型为sig的信号,不管其正在执行程序的哪一部分,就立即执行func()函数。

当func()函数执行结束后,控制权返回进程被中断的那一点继续执行。

2 SIGIGN, 忽略该信号

3 SIGDFL, 恢复系统对信号的默认处理。

返回值: 返回先前的信号处理函数指针,如果有错误则返回SIG_ERR(-1)。

注意:

当一个信号的信号处理函数执行时,如果进程又接收到了该信号,该信号会自动被储存而不会中断信号处理函数的执行,

直到信号处理函数执行完毕再重新调用相应的处理函数。

如果在信号处理函数执行时进程收到了其它类型的信号,该函数的执行就会被中断。

在信号发生跳转到自定的handler处理函数执行后,系统会自动将此处理函数换回原来系统预设的处理方式,

如果要改变此 *** 作请改用sigaction()。

4 实例

下面我们实际编码, 看看具体如何在捕捉到信号后, 打印进程堆栈, 然后结束进程

#include <iostream>

#include <timeh>

#include <signalh>

#include <stringh>

#include <execinfoh>

#include <fcntlh>

#include <map>

using namespace std;

map<int, string> SIG_LIST;

#define SET_SIG(sig) SIG_LIST[sig] = #sig;

void SetSigList(){

SIG_LISTclear();

SET_SIG(SIGILL)//非法指令

SET_SIG(SIGBUS)//总线错误

SET_SIG(SIGFPE)//浮点异常

SET_SIG(SIGABRT)//来自abort函数的终止信号

SET_SIG(SIGSEGV)//无效的存储器引用(段错误)

SET_SIG(SIGPIPE)//向一个没有读用户的管道做写 *** 作

SET_SIG(SIGTERM)//软件终止信号

SET_SIG(SIGSTKFLT)//协处理器上的栈故障

SET_SIG(SIGXFSZ)//文件大小超出限制

SET_SIG(SIGTRAP)//跟踪陷阱

}

string& GetSigName(int sig){

return SIG_LIST[sig];

}

void SaveBackTrace(int sig){

//打开文件

time_t tSetTime;

time(&tSetTime);

tm ptm = localtime(&tSetTime);

char fname[256] = {0};

sprintf(fname, "core%d-%d-%d_%d_%d_%d",

ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday,

ptm->tm_hour, ptm->tm_min, ptm->tm_sec);

FILE f = fopen(fname, "a");

if (f == NULL){

exit(1);

}

int fd = fileno(f);

//锁定文件

flock fl;

fll_type = F_WRLCK;

fll_start = 0;

fll_whence = SEEK_SET;

fll_len = 0;

fll_pid = getpid();

fcntl(fd, F_SETLKW, &fl);

//输出程序的绝对路径

char buffer[4096];

memset(buffer, 0, sizeof(buffer));

int count = readlink("/proc/self/exe", buffer, sizeof(buffer));

if(count > 0){

buffer[count] = '\n';

buffer[count + 1] = 0;

fwrite(buffer, 1, count+1, f);

}

//输出信息的时间

memset(buffer, 0, sizeof(buffer));

sprintf(buffer, "Dump Time: %d-%d-%d %d:%d:%d\n",

ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday,

ptm->tm_hour, ptm->tm_min, ptm->tm_sec);

fwrite(buffer, 1, strlen(buffer), f);

//线程和信号

sprintf(buffer, "Curr thread: %d, Catch signal:%s\n",

pthread_self(), GetSigName(sig)c_str());

fwrite(buffer, 1, strlen(buffer), f);

//堆栈

void DumpArray[256];

int nSize = backtrace(DumpArray, 256);

sprintf(buffer, "backtrace rank = %d\n", nSize);

fwrite(buffer, 1, strlen(buffer), f);

if (nSize > 0){

char symbols = backtrace_symbols(DumpArray, nSize);

if (symbols != NULL){

for (int i=0; i<nSize; i++){

fwrite(symbols[i], 1, strlen(symbols[i]), f);

fwrite("\n", 1, 1, f);

}

free(symbols);

}

}

//文件解锁后关闭, 最后终止进程

fll_type = F_UNLCK;

fcntl(fd, F_SETLK, &fl);

fclose(f);

exit(1);

}

void SetSigCatchFun(){

map<int, string>::iterator it;

for (it=SIG_LISTbegin(); it!=SIG_LISTend(); it++){

signal(it->first, SaveBackTrace);

}

}

void Fun(){

int a = 0;

int b = 1 / a;

}

static void ThreadFun(void arg){

Fun();

return NULL;

}

int main(){

SetSigList();

SetSigCatchFun();

printf("main thread id = %d\n", (pthread_t)pthread_self());

pthread_t pid;

if (pthread_create(&pid, NULL, ThreadFun, NULL)){

exit(1);

}

printf("fun thread id = %d\n", pid);

for(;;){

sleep(1);

}

return 0;

}

文件名为 btcpp

编译: g++ btcpp -rdynamic -I /usr/local/include -L /usr/local/lib -pthread -o bt

主线程创建了 fun 线程, fun 线程有一个除零错误, 系统抛出 SIGFPE 信号

该信号使 fun 线程中断, 我们注册的 SaveBackTrace 函数捕获到这个信号, 打印相关信息, 然后终止进程

在输出的core文件中, 我们可以看到简单的堆栈信息

5 善后处理

在上面的例子中, fun 线程被 SIGFPE 中断, 转而执行 SaveBackTrace 函数

此时, main 线程仍然在正常运行

如果我们把 SaveBackTrace 函数最后的 exit(1); 替换成 for(;;)sleep(1);

main 线程就可以一直正常的运行下去

利用这个特点, 我们可以做很多其它事情

游戏的服务器进程常常有这些线程:

网络线程, 数据库线程, 业务处理线程 引发逻辑错误的代码常常位于业务处理线程

而数据库线程由于功能稳定, 逻辑简单, 是十分强壮的

那么, 如果业务处理线程有逻辑错误, 我们捕捉到信号后, 可以在信号处理函数的最后,

通知数据库线程保存游戏数据

直到数据库线程把游戏信息全部存入数据库, 信号处理函数才返回

这样, 服务器宕机不会导致回档, 损失被大大降低

要实现这个机制, 要求数据库模块和业务处理模块具有低耦合度

当然, 实际应用的时候, 还有许多细节要考虑

比如, 业务处理线程正在处理玩家的数据, 由于发生不可预知的错误, 玩家的数据被损坏了, 这些玩家的数据就不应该被存入数据库

课程名称

使用教材

备注

物联网产业与技术导论

《物联网:技术、应用、标准与商业模式》,电子工业出版社,等教材。

在学完高等数学,物理,化学,通信原理,数字电路,计算机原理,程序设计原理等课程后开设本课程,全面了解物联网之RFID、M2M、传感网、两化融合等技术与应用。

C语言程序设计

《C语言程序设计》,清华大学出版社,等教材。

物联网涉及底层编程,C语言为必修课,同时需要了解OSGi,OPC,Silverlight等技术标准

Java程序设计

《Java语言程序设计教程》,机械工业出版社,等教材。

物联网应用层,服务器端集成技术,开放Java技术也是必修课,同时需要了解Eclipse,SWT, Flash, HTML5,SaaS等技术

无线传感网络概论

《无线传感器网络理论、技术与实现》,国防工业出版社,《短距离无线通讯入门与实战》北京航空航天大学出版社,等教材。

学习各种无线RF通讯技术与标准,Zigbee, 蓝牙,WiFi,GPRS,CDMA,3G, 4G, 5G,Mote等等

TCP/IP网络与协议

《TCP/IP网络与协议》,清华大学出版社,等教材。

TCP/IP以及OSI网络分层协议标准是所有有线和无线网络协议的基础,Socket编程技术也是基础技能,为必修课

嵌入式系统

《嵌入式系统技术教程》,人民邮电出版社等教材。

嵌入式系统是物联网感知层和通讯层重要技术,了解TinyOS等,为必修课

传感器技术概论

《传感器技术》,中国计量出版社,等教材。

物联网专业学生需要对传感器技术与发展,尤其是在应用中如何选用有所了解,但不一定需要了解传感器的设计与生产,对相关的材料科学,生物技术等有深入了解

RFID技术概论

《射频识别(RFID)技术原理与应用》,机械工业出版社,等教材。

RFID作为物联网主要技术之一,需要了解,它本身(与智能卡技术融合)可以是一个细分专业或行业,也可以是研究生专业选题方向。

工业信息化及现场总线技术

《现场总线技术及应用教程》,机械工业出版社,等教材。

工业信息化也是物联网主要应用领域,需要了解,它本身也可以是一个细分专业或行业,也可作为研究生专业选题方向。

M2M技术概论

《M2M: The Wireless Revolution》,TSTC Publishing,等教材。

本书是美国“Texas State Techinical College”推出的M2M专业教材,在美国首次提出了M2M专业教学大纲,M2M也是物联网主要领域,需要了解,建议直接用英文授课。

物联网软件、标准、与中间件技术

《中间件技术原理与应用》,清华大学出版社,《物联网:技术、应用、标准与商业模式》,电子工业出版社,等教材。

物联网产业发展的关键在于应用,软件是灵魂,中间件是产业化的基石,需要学习和了解,尤其是对毕业后有志于走向工业和企业界的学生。

①、对于你能写这么长的问题描述,说明你很认真。

②、你的目的性较强,但是你也想有更加明确的目标,我可以给你讲一下怎么自己去寻找目标和路线以及怎样学习。

③、计算机专业领域一共有几个大方向,十几个分支方向,而每个分支方向又有几十个小方向,每一个方向的深入学习与熟练到一定火候都不是一朝一夕,互相之间也不是完全没联系的,但是你现在就应该选择一个大方向并在其中的一个小方向内深入(为什么要这么早就选择具体的分支方向?后面说)。

④、这里列出计算机的几个大方向(非编程开发类的我就不说了):

基本方向:

1、单片机、嵌入式方向

2、网络编程:涉及到服务器程序、客户端开发、脚本设计等。

3、系统编程:基础API开发、桌面开发、系统程序开发、服务程序

4、图形学:3D、2D、图像识别、人脸识别

5、音频:语音识别、音频解码、音频软件

6、编译原理:编译器设计、脚本解释器、虚拟机、非自然语言翻译系统

7、应用层开发:利用高层语言去开发表层应用

8、安全:反工程、病毒、反病毒、木马、反木马、软件破解、软件加壳

附加方向:

8、人工智能:遗传算法、神经网络、灰色系统等等

9、游戏设计:各种游戏引擎设计以及业务逻辑设计等

⑤、基本方向是你一定要选的,附加方向只是基于基本方向的一些锦上添花,但是不管你怎么选,最开始某些东西一定要深入而不是只是懂就够(当然你对自己要求似乎不会很低),我把这个列出来:

数据结构:下面其他理论的基础。

*** 作系统原理:理解 *** 作系统的架构和细节,你才能对以后关于多线程、文件管理、内存管理、指令优先级等有一个正确理解和运用。

编译原理:能够升华你对计算机编程语言的理解,对以后出现的各种编译、解释、兼容、移植、优化、并发与并行算法等有一个深入理解。

数据库系统原理:这个是进入公司都要会的,也是大型软件开发的基础。

软件工程:这个是你能够在经验不足还能保证大项目正常完成的理论基础。

网络技术:这个是必须学的,因为目前几乎没有一款装几率很高的软件或者平台跟网络无关。

数学的话,主要是:离散数学、线性代数、高等数学、计算机图形学、概率论

以上几个基础就是你成为一个融汇各个主要分支牛人必须学的(当然不是指理论,而是理论+实践编码能力)

⑥以上都是大的基础,要一一攻破并深入学习,虽然网络时代计算机专业知识爆炸式的增长,但是以上几个基础掌握后,会发现,以后的什么新的理论和技术都是基于这些大基础,你就很容易理解了。

⑦我为什么开头不讲你要具体学什么怎么顺序学呢?因为那些技术你要掌握的话,根本可以自己解决,但是如果你由于兴趣,沉迷于一些自己可见的小范围技术的话,那么毕业后虽然也能找到不错的工作,薪水也可能高,但是不能成为一个大牛。

现在才开始讲学习顺序,虽然你说不要推荐书,不过我还是要用书来做顺序。

C语言是可以写很多核心和高级的东西,而不只是小东西,但是从你代码来看,居然用到了 goto,我不是说你那些程序用到GOTO有什么不好,而是一定要避免用GOTO,goto是错误之源,如果你有什么内容非要用到goto才能写到,说明你的编码技巧还有不少提高空间。

你的学习顺序应该是:

C:做一个超级马里奥出来,并能够读取文本脚本来更新关卡。

C++:写一个2D图形引擎,封装掉细节,实现面向对象设计和可复用设计,并且用到《设计模式》中提到的一些设计模式,这样才能算对C++有一个很好的掌握。

MFC:MFC技术虽然近期已经冷下来了,但是你能熟练掌握它,才能证明你的C++OO技术够纯熟,严格证明你掌握了MFC很简单,你只要用MFC做出一个杀毒引擎就差不多了。推荐的书有《深入浅出MFC》。

《Windows程序设计》:和MFC不同的是,用的是windows核心SDK,也就是API,这本书学完后,你才能从 *** 作系统层面上算掌握了win32 平台下的机理(其实win64和win32大部分机理类似)。

C#:C#里集合了当代和前沿计算机科学里最先进的一些语法(虽然执行效率一直被人质疑),但是你学完C#并深入后,至少能够算是对计算机语言有一个更加深刻的理解了。如何证明你C#学的不错了?也很简单,再次写一个随便什么游戏,比如俄罗斯方块。如果更加证明自己呢?用它写一个P2P网络对战游戏。

(如果你注意的话,会发现我说的学习顺序都是沿着语言和某些技术的,为什么呢?因为这些语言和技术涉及到特定的领域技术和计算机理论思想,比如学完了C#的话,就不单指学完了C#,而是把多种语言范式都学习了一遍,以及现代的程序开发思维(因为里面用到了很多让你一劳永逸的技术))

以上5个步骤都是基础大步骤,要解决的话要没1-2年应该不够。

与此同时,要尽快选出文中你感兴趣的方向作为3-5年的长期方向,不要担心过早选择分支方向会有什么损失,因为计算机很多分支是相通的,只有你把分支方向深入进去,才能真正理解很多理论的实践意义。并且一旦你在某个分支领域形成了较强的优势(比如,到公司里只有你这方面最强),那么你就是稀缺人才。

关于大方向的步骤就不说了,你主要就是要把我说的这几个基础步骤先解决,同时平时要注重大方向理论结合实际去编码和开发。

以上就是关于内联函数在哪些场合使用全部的内容,包括:内联函数在哪些场合使用、物联网工程,往嵌入式方向走。求大神给个学习路线、如何在进程崩溃后打印堆栈并防止数据丢失等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/9400887.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-28
下一篇 2023-04-28

发表评论

登录后才能评论

评论列表(0条)

保存