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年的长期方向,不要担心过早选择分支方向会有什么损失,因为计算机很多分支是相通的,只有你把分支方向深入进去,才能真正理解很多理论的实践意义。并且一旦你在某个分支领域形成了较强的优势(比如,到公司里只有你这方面最强),那么你就是稀缺人才。
关于大方向的步骤就不说了,你主要就是要把我说的这几个基础步骤先解决,同时平时要注重大方向理论结合实际去编码和开发。
以上就是关于内联函数在哪些场合使用全部的内容,包括:内联函数在哪些场合使用、物联网工程,往嵌入式方向走。求大神给个学习路线、如何在进程崩溃后打印堆栈并防止数据丢失等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)