引发C++软件异常的常见原因分析与总结(实战经验分享)

AI摘要

本文总结了C++软件开发中常见的异常原因,包括变量未初始化、内存越界等问题,并提供了排查思路。

目录

1、概述

2、引发软件异常的常见原因

2.1、变量未初始化

2.2、死循环

2.3、内存越界

2.4、内存泄漏 

2.5、空指针与野指针

2.6、内存访问违例

2.7、栈内存被当成堆内存去释放 

2.8、Stack overflow线程栈溢出

2.9、函数调用约定不一致导致栈不平衡

2.10、库与库之间不匹配

2.11、多线程未加锁与多线程死锁

2.12、GDI对象泄漏导致GDI对象总数接近或达到1万个引发异常

2.13、对包含C++类成员的结构体进行memset操作       

2.14、第三方软件模块远程注入到程序中导致程序出现异常

2.15、添加日志打印覆盖了lasterror的值

2.16、其他

3、最后


C++软件异常排查从入门到精通系列教程(核心精品专栏,订阅量已达8000多个,欢迎订阅,持续更新...)https://blog.youkuaiyun.com/chenlycly/article/details/125529931C/C++实战专栏(重点专栏,专栏文章已更新500多篇,订阅量已达6000多个,欢迎订阅,持续更新中...)https://blog.youkuaiyun.com/chenlycly/article/details/140824370C++ 软件开发从入门到实战(重点专栏,专栏文章已更新300多篇,欢迎订阅,持续更新中...)https://blog.youkuaiyun.com/chenlycly/category_12695902.htmlVC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.youkuaiyun.com/chenlycly/article/details/124272585C++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)https://blog.youkuaiyun.com/chenlycly/article/details/131405795开源组件及数据库技术(专栏文章,持续更新中...)https://blog.youkuaiyun.com/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)https://blog.youkuaiyun.com/chenlycly/category_2276111.html           在C++软件开发和维护的过程中,会遇到各式各样的软件异常,这些问题的分析和排查可能会消耗大量的时间和精力,会给软件研发流程的顺利推进带来不利的影响。本文根据近几年排查C++软件异常的实践经历与实战经验,详细地总结出引发C++软件异常的常见原因,给大家提供一些借鉴和参考,以帮助大家快速地定位问题。

本文内容是在多年的项目实践的基础上,根据排查的多个项目问题总结和归纳出来的,很有实战参考价值!很多细节与内容是书本上看不到的!来源于工程实践,服务于工程实践!

1、概述

       作为一名经验丰富的C++软件研发人员,除了要有良好的代码编写与设计能力,还要有很强的代码调试能力和异常分析能力。而这些能力的培养不是一撮而就的,是通过大量的项目实践锻炼出来的。本文要讲述的内容不仅适用于刚入门的C++开发新人,也适用于有多年工作经验的C++开发人员。

       本文所讲的C++软件异常,是广义上的C++软件运行异常,包括软件运行时逻辑上的异常以及引发软件卡死或崩溃的异常。引发C++软件异常的常见原因有变量未初始化、内存越界、内存访问违例、Stack Overflow线程栈溢出、空指针与野指针、死循环、死锁、内存泄露、GDI对象泄露、函数调用约定不一致导致的栈不平衡等,下面将对这些原因及场景进行详细地阐述。

       希望大家在了解本文的内容之后,既能在编写代码时提前感知潜在的问题,也能在出问题之后多一些排查问题的思路与手段。

本文主要以PC端的Windows平台下的C++开发为例进行讲解,不同平台及不同语言在多个方面是相通的,也具有一定的参考价值。

2、引发软件异常的常见原因

       下面就来详细地讲述一下引起软件异常的常见原因及相关场景。

2.1、变量未初始化

       这可能是许多人容易忽略的问题。如果在定义变量时没有对变量进行初始化,在Debug下某些编译器会自动对变量自动进行初始化,但在Release下编译器不会对变量初始化,此时变量的值则是分配到内存时内存中的随机值。

       对于微软C++编译器,在Debug下,未初始化的栈内存会被编译器初始化为0xCCCCCCCC,未初始化的堆内存会被编译器初始化为0xDDDDDDDD,已经释放的堆内存中会被设置为0xFEEEFEEE,这些特殊的异常值说明如下:(这些异常值标识符中不区分大小写)

上述常见异常值的详细说明,可以查看我的文章:

内存中常见异常值的说明(0xcccccccc、0xcdcdcdcd、0xfeeefeee和0xdddddddd 等)https://blog.youkuaiyun.com/chenlycly/article/details/128285918       在调试代码时,如果遇到0xCCCCCCCC、0xDDDDDDDD异常值时,可能是变量未初始化,如果访问这些变量,则会报如下的内存访问违例错误:

fdbf0c5393d740babe8abe97edfb173c.png

如果遇到0xFEEEFEEE异常值时,可能是堆内存被释放了,如果继续将其当做有效的内存地址去访问内存,则会报如下的内存访问违例错误:

6d73296948d2402c8a499df48ca163b7.png

        如果代码中使用了没有初始化的变量,可能会导致Debug和Release下运行行为的不一致(因为Debug下编译器会自动初始化,Release下未初始化的变量的值是随机值),也可能会导致不同版本操作系统上运行行为的不一致(不同版本操作系统的内存管理机制是有差异的,比如win7和win10系统)。

       具体地说,代码中使用了未初始化的变量,可能会导致代码逻辑出异常,可能会导致代码产生崩溃。比如在if条件语句中使用了未初始化的变量,会直接影响程序的走向。比如访问了一个没有初始化的C++对象指针,以该指针中存放的值作为一个C++地址去访问该对象的数据成员,可能会导致内存访问违例,引发程序崩溃。再比如使用了一个未初始化的函数指针(比如存放上层设置下来的回调函数地址的函数指针),在通过该函数指针去调用函数时会是致命的,会导致程序“跑飞”了,导致程序出现胡乱的崩溃。为什么会导致程序跑飞了呢?是因为函数指针没有初始化就去访问,函数指针的值就是个随机值,根本不是一个有效的函数地址,把这个无效值作为函数地址去call,会导致不可预料的结果,可能导致程序出现胡乱的崩溃。

       可能有人会说,只要控制好现有的代码,就能保证不会访问未初始化的变量,不一定非要一上来就将变量初始化。即便是这样,也是不可取的,因为代码后面可能会交由其他人维护,每个人编写代码的水平是有差异的,你这里可以保证没问题,别人那边可能就会出问题。

       排查0xcdcdcdcd引发的软件异常案例,可以查看我的文章:

0xcdcdcdcd异常值引发C++程序崩溃问题的详细分析https://blog.youkuaiyun.com/chenlycly/article/details/128380751       排查0xfeeefeee引发的软件异常案例,可以查看我的文章:

排查软件关闭时访问了0xfeeefeee内存地址导致内存访问违例的崩溃https://blog.youkuaiyun.com/chenlycly/article/details/125267046      排查0xddddddd引发的软件异常案例,可以查看我的文章:

访问0xdddddddd内存地址引发软件崩溃的问题排查https://blog.youkuaiyun.com/chenlycly/article/details/132631020


       在这里,给大家重点推荐一下我的几个热门畅销专栏,欢迎订阅:(博客主页还有其他专栏,可以去查看)

专栏1:【C++软件异常与异常排查从入门到精通系列教程】(该精品技术专栏的订阅量已达到50000多个,专栏中包含大量项目实战分析案例,有很强的实战参考价值,广受好评!专栏文章持续更新中,已经更新到200篇以上!欢迎订阅!)

C++软件调试与异常排查从入门到精通系列文章汇总https://blog.youkuaiyun.com/chenlycly/article/details/125529931

本专栏根据多年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法,详细讲述了C++软件的调试方法与手段,详细介绍分析C++软件问题的常用分析工具,以图文并茂的方式给出具体的项目问题实战分析实例(详细讲述分析排查过程,很有实战参考价值),带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!

考察一个开发人员的水平,一是看其编码及设计能力,二是要看其软件调试能力!所以软件调试能力(排查软件异常的能力)很重要,必须重视起来!能解决一般人解决不了的问题,既能提升个人能力及价值,也能体现对团队及公司的贡献!

专栏中的文章都是通过项目实战总结出来的,包含大量项目问题实战分析案例,有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!

专栏2:【C/C++实战进阶】(该专栏涵盖了C++多方面的内容,是当前重点打造的专栏,订阅量已达30000多个,专栏文章已经更新到500多篇,持续更新中...)

C/C++实战进阶(专栏文章,持续更新中...)https://blog.youkuaiyun.com/chenlycly/category_11931267.html

以多年的开发实战为基础,总结并讲解一些的C/C++基础与项目实战进阶内容,以图文并茂的方式对相关知识点进行详细地展开与阐述!专栏涉及了C/C++领域多个方面的内容,包括C++基础及编程要点(模版泛型编程、STL容器及算法函数的使用等)、数据结构与算法、C++11及以上新特性(开源代码中可能会用到很多新特性(比如WebRTC开源库),日常编码中也会用到部分新特性,面试时也会频繁地涉及到,学习新特性很有必要)、常用C++开源库的介绍与使用(比如SQLite、libcurl、libwebsockets、libevent、jsoncpp/RapidJson、Redis、RabbitMQ、MongoDB、MQTT、ZooKeeper、OpenCV、FFmpeg、SDL、GStreamer、Live555、ReactOS等)、代码分享(调用系统API、使用开源库)、常用编程技术(动态库、多线程、多进程、数据库及网络编程等)、软件UI编程(Win32/duilib/QT/MFC)、C++软件调试技术(引发C++软件异常的常见原因分析与总结、排查C++软件异常的手段与方法、分析C++软件异常的基础知识、使用常用软件分析工具分析C++软件问题、多个项目实战问题分析案例分享等)、设计模式(单例模式、工厂模式、观察者模式、状态模式等)、网络基础知识与网络问题分析进阶内容(实战问题分析实例分享)等。本专栏的内容都是建立在项目实践的基础上,来源于项目实战,服务于项目实战,很有实战参考价值!

专栏3:【分析C++软件问题的实用软件与高效工具实战案例集锦】

C++常用软件分析工具从入门到精通案例集锦汇总(专栏文章,持续更新中...)https://blog.youkuaiyun.com/chenlycly/article/details/131405795

常用的C++软件辅助分析工具有SPY++、PE工具、Dependency Walker、GDIView、Process Explorer、Process Monitor、API Monitor、Clumsy、Windbg、IDA Pro以及内存泄漏检测工具等,本专栏详细介绍如何使用这些工具去巧妙地分析和解决日常工作中遇到的问题,很有实战参考价值!

专栏4:【VC++常用功能代码封装】

VC++常用功能开发汇总(专栏文章,持续更新中...)https://blog.youkuaiyun.com/chenlycly/article/details/124272585

将10多年C++开发实践中常用的功能,以高质量的代码展现出来。这些常用的高质量规范代码,可以直接拿到项目中使用,能有效地解决软件开发过程中遇到的问题。

专栏5:【C/C++软件开发从入门到实战】(本专栏涵盖了C++多方面的内容,是当前重点打造的专栏,专栏文章已经更新到300多篇,持续更新中!欢迎订阅!) 

C++ 软件开发从入门到精通(专栏文章,持续更新中...)https://blog.youkuaiyun.com/chenlycly/category_12695902.html

根据多年C++软件开发实践,详细地总结了C/C++软件开发相关技术实现细节,分享了大量的实战案例,很有实战参考价值。


2.2、死循环

736cdbb8267a4a8cbb2c5a4668a6d3a9.png

       死循环一般会引发CPU高占用率,一般不会导致软件崩溃。一旦发现进程占用的CPU过高,可能就是死循环触发的,当然也有可能是新开线程的线程函数中没有添加sleep导致的(线程中不能一直在处理事务,要适当的“休息休息”)。

       死循环一般是for循环或while循环的循环控制条件出了问题,可能是循环控制条件写错了(比如编写代码时的手误,比如将小于号写成了等于号,导致循环条件一直成立,循环一直退不出来),也可能是循环控制条件中变量出现了异常大的值(这个值可能是服务器返回的异常值,应该要添加值的合理性判断)。这两类问题我们在联调时都遇到过。

       另外,还有一种情况是消息上触发的函数调用上的死循环。比如调用一个底层的接口,收到底层回应消息后,又调用了该底层接口,又收到底层的这个消息,循环往复地执行同样的函数调用,导致了死循环的发生。这样的例子,我们遇到过不止一次了。

       再比如在窗口消息WM_MESSAGE1的处理函数A中调用了B函数,B函数又调用了C函数,C函数中又调用SendMessage发出了WM_MESSAGE1,由于SendMessage是直接把消息发给窗口处理过程,等消息被处理完后才会返回,相当于代码是同步执行的,所以又进入了A函数中,这样就形成了消息触发的函数调用的死循环,我们在项目中曾经遇到过一次。

        死循环会导致所在线程的卡顿或堵塞,如果发生在UI线程中,UI界面会出现点击没反应或者反应很慢的情况。对于Windows程序,可以使用Windbg或者Process Explorer去定位死循环所在线程及函数。

Windbg和Process Explorer是排查死循环和CPU高占用问题的利器,都可以查看到进程中所有线程的CPU占用情况,可以进一步查看CPU占用高的线程的函数调用堆栈。相比较而言,Process Explorer要更易操作一点,但就调试功能而言,还是Windbg要强大很多。哪些场景下使用哪种工具,视个人喜好及问题的具体情况而定吧。

       另外,这两个工具是需要pdb符号库文件的,因为要显示详细的函数调用堆栈信息(显示具体的函数名)是需要pdb符号库的(符号库中有函数及变量的详细信息)。对于windbg,需要将pdb文件的路径设置到windbg中。对于Process Explorer,只需要将pdb放置在目标exe的同级目录中,Process Exploer回到exe程序所在的目录 中去自动搜索并加载pdb文件的。

       关于排查CPU占用高的案例,可以查看文章:

借助工具软件Clumsy和Process Explorer定位软件高CPU占用问题https://blog.youkuaiyun.com/chenlycly/article/details/120931072使用Process Explorer/Process Hacker和Windbg初步定位软件高CPU占用问题https://blog.youkuaiyun.com/chenlycly/article/details/134112933

2.3、内存越界

       内存越界是指操作某个变量内存时,超过了该变量的内存范围,越界到该变量内存后面的内存上了。内存越界会篡改越界部分的内存中的内容,但内存越界不一定会导致崩溃,越界的内存区域可能是其他变量的内存区域,所以可能会篡改了其他变量的值。其他变量的值被篡改了,可能会导致软件业务出现问题,可能导致代码的运行控制逻辑出异常。

       内存越界包含栈内存越界、堆内存越界以及全局内存越界,这些类型的内存越界,我们之前都遇到过。

1)函数的局部变量是栈上分配内存的,对栈内存的越界称之为栈内存越界;

2)通过new和malloc等动态申请内存的,都是在堆上分配的,对堆内存的越界称之为堆内存越界。

3)全局变量和static静态变量都在全局内存上分配内存的,对全局内存的越界称之为全局内存越界。

对于程序内存分区,可参见文章:

实例详解C++程序的五大内存分区https://blog.youkuaiyun.com/chenlycly/article/details/120958761       对于栈内存越界,有可能越界到当前函数的其他局部变量上,另外在函数调用时,主调函数的返回地址、主调函数的ebp栈基址、用于esp校验的cookie值等都存在栈上的,有可能会越界到这些内存区域上,如果内存越界将这些内存破坏掉了,则会引起比较致命的错误。比如篡改了主调函数的返回地址,那么等被调函数返回时,要执行主调函数返回地址处的汇编代码,但这个返回地址被篡改了,是有问题的,所以程序就“跑飞”了,会产生莫名其妙的崩溃。主调函数的ebp栈基址是被用来回溯函数调用堆栈的,如果主调函数的栈基址被破坏,则会导致崩溃时无法回溯出函数调用堆栈,即出现崩溃时看不到有效的函数调用堆栈了。

就问题排查的难度而言,堆内存越界和全局内存越界比较难查,栈内存越界排查起来要容易很多。

       内存越界常见的表现形式有数组越界,操作指针指向的buffer越界等。一般都是对这些内存操作时,超过了内存的范围,主要是向后越界,可能是通过数组下标操作数组或buffer的内存,下标超过了申请内存的最大长度。

       以前我们遇到一种向前越界的情况,我们使用数组下标操作一段buffer内存,结果出现了下标为-1的情况,比如szBuf[-1],这样就越界到szBuf buffer的前面去了,当时报了如下的错误:

962bc81ad5544b5d9fcfdd7b66e28ca1.png

       其中有一种情形下的越界(被调用函数越界越到主调函数的栈内存上),很具有隐蔽性,排查起来比较困难。比如A库依赖B库,B库定义了结构体Struct1,A库调用了B库的GetData接口, GetData接口是Struct1结构体作为参数的(传入的结构体对象引用或地址),GetData函数内部进行了数据的memcpy操作。因为库发布的问题,导致两个库版本不一致,假设A库是老版本,B库是新版本。新版本B库中在Struct1结构体中新增了字段(使用了新版本的结构体),但是A库中使用的还是老的结构体,这样在调用GetData传入结构体地址或引用,由于GetData中进行了memcpy操作导致内存越界:

// 1、A库中的代码:
Struct1 st1; // A库使用的是老版本的结构体,
GetData(&st1);

// 2、B库提供的GetData接口,传入的参数是结构体引用
void GetData( out Struct1& st )
{
    // 假设B库中定义的是一个全局变量g_st,即:Struct1 g_st,该变量存的是B库中的信息
    // 把全局变量g_st中的信息拷贝到st中,传出去(参数st是传出参数)    
    memcpy(&st, &g_st, sizeof(st));
}

       因为B库在编译时使用了新的Struct1结构体(结构体末尾新增了一个成员字段),所以memcpy中的sizeof(st)是新的结构体字段,所以memcpy执行内存拷贝时的内存操作长度是新结构体的长度。但Getdata传入的是引用,所以memcpy的目标内存是在A模块的主调函数的栈内存上,而A模块中的主调函数传入的结构体对象用的是老的结构体(没有新增字段),所以GetData中产生的内存越界直接越界到位于A模块中的主调函数的栈内存上了。可能篡改了主调函数中其他栈变量的内存或者其他信息,可能会导致代码出现unexpected不可预期、不可解释的异常运行行为。这样的问题我们已经遇到过多次了。这类问题比较有隐蔽性,如果没有经验,排查起来会很困难

对于内存越界,我们可以通过添加多行打印日志、分块注释代码、添加数据断点(Visual Studio中支持数据断点)等手段进行定位。

       关于排查内存越界的案例,可以查看我的文章:

巧用Visual Studio中的数据断点去排查C++内存越界问题https://blog.youkuaiyun.com/chenlycly/article/details/125626617播放WebRTC开源库回调出来的视频码流时遇到的内存越界问题排查https://blog.youkuaiyun.com/chenlycly/article/details/131222627

2.4、内存泄漏 

       内存泄漏是指程序中通过new/malloc动态申请的堆内存在使用完后没有释放,长时间频繁执行这些没有释放堆内存的代码,会导致程序的内存会逐渐被消耗,程序运行会变慢,直到内存被耗尽(Out of memory),程序闪退。程序闪退时,系统会弹出如下的Out of Memory的报错提示框:

9a833e3b669a43bbb5907eafd2ffde34.png

如何才能察觉出程序有内存泄漏呢?其实很简单,让程序持续运行,然后查看任务管理器中程序进程使用的内存变化情况:

736cdbb8267a4a8cbb2c5a4668a6d3a9.png

如果进程占用的内存一直不不断上涨且不回落,则说明进程中可能存在内存泄漏。 

       那内存泄漏该如何排查呢?在Linux系统中,有个强大的内存调试和监测工具Valgrind,该工具排查内存泄漏很好用,但该工具只支持Linux系统,没有Windows版本。目前Windows平台上很多内存泄露检测工具都比较陈旧、不再更新了,不再支持VS2015及以上版本编译出来的程序了。之前尝试使用腾讯的tMemMonitor内存泄露检测工具 ,但很多场景下的内存泄露,该工具都检测不出来。

       Windows平台下可以使用Windbg中的umdh.exe工具排查内存泄漏,该工具监测内存泄漏的步骤稍显麻烦,可以参见之前写过的一篇文章:(umdh.exe工具在检测时不需要重新编译程序源码,直接就可以检测)

使用Windbg定位Windows C++程序中的内存泄露https://blog.youkuaiyun.com/chenlycly/article/details/121295720      也可以使用Visual Leak Detector工具去检测内存泄露,可以查看我的文章:(使用Visual Leak Detector时需要重新编译程序源码)

使用Visual Leak Detector排查内存泄漏问题https://blog.youkuaiyun.com/chenlycly/article/details/133041372

2.5、空指针与野指针

        空指针和野指针是使用指针时两类很常见的错误,访问空指针和野指针都会导致内存访问违例,导致程序崩溃。

       所谓空指针是指一个指针变量的值为空,如果把该指针变量的值(值为0)作为地址去访问其指向的数据类型,就会引发问题。

       对于Windows系统,访问空指针会之所以会产生崩溃,是因为访问了Windows系统预留的64KB禁止访问的空指针内存区(即0-64KB这个区间的小地址内存区域),这是Windows系统故意预留的一块小地址内存区域,是为了方便程序员定位问题使用的。一旦访问到该内存区就会触发内存访问违例,系统就会强制将进程强制结束掉。

       关于64KB禁止访问的小地址内存区域,在《Windows核心编程》一书中内存管理的章节,有专门的描述,相关截图如下所示:

2d0fcc3a54a943819051e337face6547.png

       比如一个C++指针变量值为空(NULL对应的值为0),如果通过该指针去访问其指向的类对象的数据成员,就会访问到64KB的小地址内存区,就会触发异常。因为会将指针变量中的NULL值作为C++对象的首地址,通过类数据成员的内存分布,C++类对象的数据成员的内存地址等于类对象的首地址加上一个offset偏移地址,这样该C++类对象的数据成员的内存地址比较小(小于64KB),要读该数据成员变量的值,就是对其内存地址进行寻址(从内存中读取内存中存放的内容),这样就访问了很小的内存地址,所以触发了内存访问违例。

       所谓野指针,是指该指针指向的内存(指针变量中存放的值,就是其指向的内存的地址)已经被释放了,但还去访问该指针指向的内存,一般会导致内存访问违例,导致软件崩溃。还有一种情形是同一段堆内存被delete或free了两次,也会触发崩溃。

       引发C++程序内存错误的详细说明,可以参见我的文章:

引发C++程序内存错误的常见原因分析与总结https://blog.youkuaiyun.com/chenlycly/article/details/128599525

2.6、内存访问违例

       前面已经讲了内存越界,内存越界肯能会导致内存访违例,但内存访问违例,不仅仅是内存越界导致的,所以这个地方要单独捻出来单独说一下。

0c908d1d61ef4c3ea5c8ca5eb48683b8.png

       内存访问违例包含读内存违例和写内存违例。比如上面讲到的Windows下的小内存地址(64KB内存区域)是禁止访问的,一旦访问就会触发内存访问违例,系统会强制将进程终止掉。再比如上面讲的内存越界,也会触发内存访问违例。

       再比如系统出于安全考虑,用户态的模块是禁止访问内核态地址的,比如32位Windows程序,系统会分配4GB的虚拟地址空间,一般用户态和内核态各占2GB,用户态的内存地址是小于2GB的,如果我们通过一些分析软件发现发生崩溃的那条汇编指令中访问的内存地址大于2GB,则肯定是禁止访问内核态地址触发的内存访问违例,肯定是代码中把地址搞错了,访问了不该访问的地址。

还有一点需要说明一下,即使程序访问了不属于自己的地址,比如内存越界,不一定会触发内存访问违例,程序不一定会崩溃。程序访问了不属于当前代码块的内存地址,系统允许你访问(读数据或写数据),你就可以访问不会有内存访问违例发生;系统不允许你访问,就会触发内存访问违例,就会引发崩溃。

2.7、栈内存被当成堆内存去释放 

       在栈上分配内存的C++类对象,是不能用delete去释放内存的,delete释放的是堆内存,否则会导致异常崩溃。

       之前在使用一个框架库导出类ClassA(假设类名叫ClassA),在框架库内部的框架中会自动去delete这个类对象。但我们是在一个函数中使用该类定义一个局部变量(类对象),即:

void Func()
{
    ClassA clsA;

    // ......
}

该类对象在栈上分配内存的,是不能用delete去释放的,应该调用接口给该对象设置不需要框架自动销毁。对于栈上分配内存的局部变量clsA,在函数退出时其占用的栈内存会自动释放。

2.8、Stack overflow线程栈溢出

       单个线程的栈空间是有限的,比如Windows线程的默认栈空间大小是1MB,当然我们创建线程时也可以指定线程栈的大小,但一般不宜过大。

       线程的栈空间是用来干嘛的呢?某个时刻某个线程实际使用的栈空间,等于当前线程函数调用堆栈中所有函数占用的栈空间总和。函数中的局部变量是在所在线程的栈内存上分配的,函数调用的参数也是通过栈内存传递(参数值入栈)给被调用函数的,这两点就是函数占用栈内存的主要对象。

       一旦当前线程的调用堆栈中占用的总的栈空间超过当前线程的栈空间上限,就会产生stack overflow线程栈溢出的异常,如下所示:

654668e5ade14535b177e5423b510e0a.png

导致程序崩溃。

       线程栈溢出问题的排查实例,可以参见我写的文章:

线程栈溢出异常,程序崩溃在汇编代码test dword ptr [eax],eax上的问题排查https://blog.youkuaiyun.com/chenlycly/article/details/131743305

2.9、函数调用约定不一致导致栈不平衡

       C++中常用的调用约定有__cdecl C调用、__stdcall标准调用、__fastcall快速调用。其中,__cdecl是C/C++默认的调用方式,C/C++运行时库中的函数都是__cdecl调用。__stdcall标准调用是Windows系统提供的系统API函数的调用约定。

       函数的调用约定不仅决定着函数多个参数压入栈中的先后顺序,还决定了应该由谁来释放主调函数给被调函数传递的参数所占用的栈空间(是主调函数释放参数占用的栈空间,还是被调函数去释放参数占用的栈空间)。函数调用时栈分布如下:

db2ee2e70e3947d28042a3b5dded916f.png

对于由谁来释放栈空间,以常用的__stdcall标准调用约定和__cdecl调用约定为例,如果被调用函数是__stdcall标准约定,则由被调函数去释放传给被调函数的参数占用的栈空间。如果被调函数是__cdecl调用,则由主调函数去释放参数占用的栈空间。

       关于谁来负责释放参数占用的栈空间,大家很容易混淆,给大家一个容易记住的办法。比如我们经常用到的C函数printf:

该函数支持多个可变参数的格式化,设定的是C调用约定,因为被调函数是无法知道传入了哪些参数,只有主调函数才知道传入了哪些参数,才知道传入参数占用的栈内存的大小,所以只能是主调函数去释放参数占用的栈内存。

        函数调用约定引发的栈不平衡问题在设置回调函数时比较常见,特别是跨语言设置回调函数时。因为调用约定的不一致,可能会导致参数栈空间多释放了一次,会直接影响主调函数的ebp栈基址出错,导致主调函数中的内存地址错乱出现异常或崩溃。比如C#程序调用C++实现的SDK,因为C++语言中默认使用__cdecl C调用约定,C#默认使用__stdcall标准调用,如果回调函数没有显式地指明调用约定,在实际使用时就会出问题。

       在Debug下,Visual Studio默认开启了/RTC(Run-Time Check)运行时检测,如下:

25e2ca0499d54dddb910fa8cdae7ead4.png

其中/RTCsu选项的微软官方说明如下:

f3127161fbfa4f95b3989864799fe602.png

/RTC编译选项在函数退出时会监测栈是否平衡,一旦检测到栈不平衡,一般都会弹出如下的提示:

d3882d38835b46efbdc7f25bc56bec6c.png

        默认情况下,/RTC编译选项只在Debug下是开启的,Release下该选项是关闭的。有的模块为了方便排查问题,在Release版本中开启了该编译选项。开启该选项后,在代码编译时会向代码添加很多额外的跟踪代码,会对程序的执行效率产生一定的影响。

       关于函数调用约定的详细说明,可以查看我的文章:

C/C++函数的调用约定详解https://blog.youkuaiyun.com/chenlycly/article/details/125354572


  在这里,给大家重点推荐一下我的几个热门畅销专栏,欢迎订阅:(博客主页还有其他专栏,可以去查看)

专栏1:【C++软件异常与异常排查从入门到精通系列教程】(该精品技术专栏的订阅量已达到50000多个,专栏中包含大量项目实战分析案例,有很强的实战参考价值,广受好评!专栏文章持续更新中,已经更新到200篇以上!欢迎订阅!)

C++软件调试与异常排查从入门到精通系列文章汇总https://blog.youkuaiyun.com/chenlycly/article/details/125529931

本专栏根据多年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法,详细讲述了C++软件的调试方法与手段,详细介绍分析C++软件问题的常用分析工具,以图文并茂的方式给出具体的项目问题实战分析实例(详细讲述分析排查过程,很有实战参考价值),带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!

考察一个开发人员的水平,一是看其编码及设计能力,二是要看其软件调试能力!所以软件调试能力(排查软件异常的能力)很重要,必须重视起来!能解决一般人解决不了的问题,既能提升个人能力及价值,也能体现对团队及公司的贡献!

专栏中的文章都是通过项目实战总结出来的,包含大量项目问题实战分析案例,有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!

专栏2:【C/C++实战进阶】(该专栏涵盖了C++多方面的内容,是当前重点打造的专栏,订阅量已达30000多个,专栏文章已经更新到500多篇,持续更新中...)

C/C++实战进阶(专栏文章,持续更新中...)https://blog.youkuaiyun.com/chenlycly/category_11931267.html

以多年的开发实战为基础,总结并讲解一些的C/C++基础与项目实战进阶内容,以图文并茂的方式对相关知识点进行详细地展开与阐述!专栏涉及了C/C++领域多个方面的内容,包括C++基础及编程要点(模版泛型编程、STL容器及算法函数的使用等)、数据结构与算法、C++11及以上新特性(开源代码中可能会用到很多新特性(比如WebRTC开源库),日常编码中也会用到部分新特性,面试时也会频繁地涉及到,学习新特性很有必要)、常用C++开源库的介绍与使用(比如SQLite、libcurl、libwebsockets、libevent、jsoncpp/RapidJson、Redis、RabbitMQ、MongoDB、MQTT、ZooKeeper、OpenCV、FFmpeg、SDL、GStreamer、Live555、ReactOS等)、代码分享(调用系统API、使用开源库)、常用编程技术(动态库、多线程、多进程、数据库及网络编程等)、软件UI编程(Win32/duilib/QT/MFC)、C++软件调试技术(引发C++软件异常的常见原因分析与总结、排查C++软件异常的手段与方法、分析C++软件异常的基础知识、使用常用软件分析工具分析C++软件问题、多个项目实战问题分析案例分享等)、设计模式(单例模式、工厂模式、观察者模式、状态模式等)、网络基础知识与网络问题分析进阶内容(实战问题分析实例分享)等。本专栏的内容都是建立在项目实践的基础上,来源于项目实战,服务于项目实战,很有实战参考价值!

专栏3:【分析C++软件问题的实用软件与高效工具实战案例集锦】

C++常用软件分析工具从入门到精通案例集锦汇总(专栏文章,持续更新中...)https://blog.youkuaiyun.com/chenlycly/article/details/131405795

常用的C++软件辅助分析工具有SPY++、PE工具、Dependency Walker、GDIView、Process Explorer、Process Monitor、API Monitor、Clumsy、Windbg、IDA Pro以及内存泄漏检测工具等,本专栏详细介绍如何使用这些工具去巧妙地分析和解决日常工作中遇到的问题,很有实战参考价值!

专栏4:【VC++常用功能代码封装】

VC++常用功能开发汇总(专栏文章,持续更新中...)https://blog.youkuaiyun.com/chenlycly/article/details/124272585

将10多年C++开发实践中常用的功能,以高质量的代码展现出来。这些常用的高质量规范代码,可以直接拿到项目中使用,能有效地解决软件开发过程中遇到的问题。

专栏5:【C/C++软件开发从入门到实战】(本专栏涵盖了C++多方面的内容,是当前重点打造的专栏,专栏文章已经更新到300多篇,持续更新中!欢迎订阅!) 

C++ 软件开发从入门到精通(专栏文章,持续更新中...)https://blog.youkuaiyun.com/chenlycly/category_12695902.html

根据多年C++软件开发实践,详细地总结了C/C++软件开发相关技术实现细节,分享了大量的实战案例,很有实战参考价值。


2.10、库与库之间不匹配

        因为一些原因,导致dll库与dll库之间的版本不一致或不匹配,从而导致程序运行异常或崩溃。

       比如底层的库只发布了Debug版本的库,忘记发布Release版本,导致Debug版本库与Release库混用,因为Debug与Release下的内存管理机制的不同会导致崩溃。Debug下申请内存时会额外分配一些用于存放调试信息的内存,而Release下是没有的。

       再比如,底层库的API头文件发生了改动(比如结构体中新增或删减了若干字段),但只发布了库文件,忘记发布头文件,导致使用该底层库的上层库使用的还是老版本的头文件。即底层库是用新的头文件编译的,而上层库使用的是老版本头文件编译的,用到改动的结构体时在内存上就会有问题,上面的有个小节就提到这样的问题。

       还比如,我们修改了头文件,但发布时有若干关联的库没有编译或者编译失败了,导致程序安装包中使用的还是之前的老版本的库,这样也会导致库与库之间的不匹配。

        一般这类库的不匹配会触发内存上的问题,会让程序出现异常或崩溃,比如Debug下弹出如下的提示框:

bf274a6ac9204ffe86cd2de103926adc.png

处理这类问题的办法是,查看svn或者git上的库修改或发布记录,可能要重新发布库,也可能需要将相关的模块重新编译一下。

       关于Debug库与Release库混用导致的软件异常排查案例,可以参看我的文章:

Release库与Debug库混用导致释放堆内存时产生异常的详细分析https://blog.youkuaiyun.com/chenlycly/article/details/128503700

2.11、多线程未加锁与多线程死锁

       多线程在访问同一个共享内存资源时,如果没有对共享资源加锁,可能会引发异常。比如项目中遇到的一个典型问题,一个线程在对STL列表进行元素增加与删除操作,另一个线程在遍历该STL列表,则可能会出现同时操作时的异常。        

       而对多个线程访问的共享资源加锁后,如果处理不当,则可能会导致多线程之间发生死锁,比如线程1占用了锁A,在等待获取锁B,而线程2占用了锁B,在等待获取锁A,两个线程各不相让,在没有获取到自己要获取的锁之前,都不会释放另一个锁,这样就导致了死锁。我们需要做好多个线程间协调,避免死锁问题的出现。很多时候我们能够根据现象及相关的打印日志,初步估计出可能发生死锁的地方。

       如果UI线程出现堵塞,或者是底层业务模块出现拥堵,业务出现异常,可能就是死锁引起的。可以将windbg挂在到目标进程上,查看所有线程的函数调用堆栈,确定发生死锁的是哪些线程,发生死锁的线程一般都会停留在WaitForSingleObject这个函数的调用上,比如这样的截图:

如图所示,当前线程卡在了WaitForSingleObject的函数调用上。通过函数调用堆栈,可以确定是调用了哪个函数触发的。

       对于使用临界区的死锁,使用Windbg排查比较容易分析,因为临界区是属于用户态的,我们只需要使用为Windbg进行用户态的调试即可。如果是信号量等其他的锁,则要使用Windbg进行内核态的调试,内核态的调试则要复杂很多。

       关于多线程死锁排查的案例,可以查看我的文章:

使用Windbg分析多线程临界区死锁问题分享https://blog.youkuaiyun.com/chenlycly/article/details/128532743

2.12、GDI对象泄漏导致GDI对象总数接近或达到1万个引发异常

        通过查看任务管理器就可以看到目标进程的GDI占用情况:

       当程序中有GDI对象泄露时,程序长时间拷机运行,可能就会出现GDI对象接近或达到1万个,导致GDI绘图函数调用出现异常,出现窗口绘制不出来等情况。

       除了GDI泄漏会导致GDI总数达到系统上限,打开程序的多个窗口可能也会导致这个问题,比如之前我们用MFC做UI界面时,每个控件都是一个窗口,每个窗口都包含若干个GDI对象,这样导致一个聊天窗口就占用了200多个GDI对象。这样在测试同事做极限测试时,同时打开了好几十个聊天窗口,就出现了GDI对象达到或接近上万个的问题。这也是当时我们要将MFC界面库换成duilib界面库的原因之一。

       对于GDI对象泄露,可以使用GDIView工具去查看目标进程中的GDI对象的占用及增长情况,然后结合代码排查出问题。

      关于使用GDIView排查GDI对象泄露的案例,可以查看我的文章:

使用GDIView工具排查GDI对象泄漏问题(常用分析工具)https://blog.youkuaiyun.com/chenlycly/article/details/125399896使用GDIView排查GDI对象泄漏导致的程序UI界面绘制异常问题https://blog.youkuaiyun.com/chenlycly/article/details/133413566

2.13、对包含C++类成员的结构体进行memset操作       

       大家在使用结构体对象时,在使用结构体之前,都会习惯性地对结构体对象进行memset操作,但如果结构体中包含C++类时,是不能进行memset操作的,我们需要在构造函数中对结构体对象成员进行初始化。

       对包含C++类的结构体对象进行memset操作导致的崩溃问题,我们已经遇到过几次了,特别是新人容易犯这样的错误。有的C++类除了有存放数据的成员,还有维护内部内存结构的字段,比如string类、CString类、stl类等,如果对结构体对象进行memset操作,则会破坏维护内部内存结构的字段的内存,会导致不可预期的错误。例如CString类中就包含了一个额外的用于维护类内部内存结构的类:

具体的问题实例,比如在以前遇到的一个问题场景中,调试时发现,传给C++类对象的数据是对的,但是用到这个C++类对象时获取的值却是有问题的。

       再比如,几年前同事在开发新功能时,遇到了一个很诡异的问题,代码是顺序执行的,按顺序执行下来肯定是没问题的,但是程序跑下来,逻辑却有明显的异常。于是我过去看了一下,发现他定义的结构体中包含有stl列表:

他在使用该结构体对象之前,习惯性对结构体对象进行了memset操作,问题就出在这个memset操作上了。memset操作破坏了stl列表内部的内存结构,在我们读取这个列表中的数据时,stl内部抛出了异常,直接将当前函数余下的代码给跳过去了,导致本该执行到的代码没有执行,导致了程序逻辑上的异常。

上述结构体中已经在构造函数中对结构体的成员变量进行了初始化,在用结构体定义变量时就不需要额外初始化了。

2.14、第三方软件模块注入到程序中导致程序出现异常

       我们之前遇到过很多次这样的问题,输入法的库注入到我们软件的进程中,导致了我们软件的崩溃。通过分析发现,崩溃是发生在输入法的模块中,但因为这个模块是注入到我们的进程中的,所以直接导致了我们软件的崩溃。此外,输入法注入到我的进程后还出现了软件卡顿、CPU占用不间断跳高的问题。

       除了输入法,还有一些安全软件也会注入到我们的进程中,导致我们的软件出现异常,这样的问题我们也遇到过几次。比如前段时间我们就遇到这样一个问题,有个客户的机器上安装了多个安全防护软件,运行我们的软件一段时间后就会崩溃闪退,经远程查看到任务管理器中的进程的内存在持续增长,软件中发生了内存泄露,增长到一定程度后就导致内存耗尽,发生了闪退。最终定位到内存泄露发生在安全软件的注入库中。

       再比如几年前遇到的一个客户问题,他们的Windows系统中安装了VPN软件,注入到我们的进程中,hook了网络通信的相关接口,以监控软件的网络数据包的收发,其中hook的recvfrom接口实现有bug,我们代码中有处调用recvfrom接口的地方传入了两个NULL参数(对于系统API函数recvfrom,传入NULL值是允许的),结果直接导致该注入模块产生了崩溃,进而导致了我们软件的崩溃。

       查看套接字API函数recvfrom的说明,函数的最后两个参数是可选的,可以不传入,直接设置NULL就可以了,如下所示:

但客户VPN软件注入模块,将系统的recvfrom函数hook成了他们实现的recvfrom函数,在实现他们自己的recvfrom函数时,直接访问了recvfrom最后的两个参数,而我们的代码直接传入了NULL值:

这样在他们的recvfrom内部访问了NULL指针,触发了内存访问违例,导致VPN软件的注入模块发生崩溃,从而导致了我们整个程序的崩溃。

       像这类出在第三方安全软件中的问题,必须要拿出足够的证据,证明问题是出在安全软件上,客户才会认可排查的结论,客户才会找第三方安全软件开发商反馈问题。对于本例中的问题,我们有个临时的规避办法,我们只要传入两个有效的参数即可,当然在对应的代码中,我们并不关心这两个参数在函数调用完成之后的返回值,不再传入两个NULL参数。

2.15、添加日志打印覆盖了lasterror的值

       之前在开发新版本的需求时,发现我们的客户端软件始终连不上某个业务服务器,抓包看到客户端发起了三次握手的流程,客户端发出了SYN包,服务器收到SYN包后给出了ACK回应,但是客户端始终没给出ACK报应答,导致建链失败。

       最开始我们怀疑是客户端所在的系统的防火墙拦截了服务器的数据包,导致客户端应用层无法收到服务器的数据包。于是将程序添加到防火墙的信任列表中,允许通过防火墙进行通信,但还是有问题。于是分别将防火墙和杀毒软件都关闭掉,但是问题还是照旧。

       后来通过排查代码得知,建链时用的是非阻塞式套接字,发起connect之后会检测connect设置的lasterror值,结果在检查该lasterror之前,添加了一句打印:

就是这句打印引起的。打印接口中调用了系统API函数或者C函数,覆盖了connect设置的lasterror,导致后续判断connect设置的lasterror值出错了:

然后客户端直接关闭了套接字,结束了三次握手的流程,所以客户端始终连接不上服务器。

       这种问题我们遇到了好几次了。本例中的问题出在开源的libjingle代码(XMPP客户端代码)中,开源代码中做了多层函数封装,在多个函数返回后才去判断connect设置的lasterror值,在我们不太熟悉代码的情况下,添加了一句打印导致了这个问题。

2.16、其他

       除了上面讲到的这些异常,还有一些其他异常,比如除法表达式中的除0崩溃,比如表达式的计算结果过大超出了变量类型所能表示的最大值,导致变量溢出了等。

       除了要了解这些常见的软件异常类型,还需要掌握一些常用的软件调试技能,可以查看文章:

Visual Studio高效调试手段与技巧总结https://blog.youkuaiyun.com/chenlycly/article/details/124884225

       此外,有时还要掌握一些辅助异常排查的汇编基础知识,关于为什么要学习汇编、学习汇编有哪些好处以及如何学习汇编,可以查看我的文章:

C/C++程序员为什么要了解汇编?了解汇编有哪些好处?如何学习汇编?https://blog.youkuaiyun.com/chenlycly/article/details/142795872

C/C++程序员很有必要去了解汇编,了解汇编不仅可以很好地搞清楚高级语言代码难以理解的代码执行细节,还可以去辅助排查C/C++程序在运行过程中遇到的多种异常崩溃问题。我们在学习汇编时,要尽量将汇编与日常工作结合起来,从汇编的角度去理解工作中遇到的一些问题,必要时查看汇编代码上下文去辅助排查C/C++软件异常问题。通过日常实践,逐渐熟悉汇编,加深对汇编的认识与理解。

       关于各种异常分析项目实战案例,可以查看我的《C++软件调试与异常排查从入门到精通教程》专栏:

C++软件调试与异常排查从入门到精通专栏介绍与文章汇总https://blog.youkuaiyun.com/chenlycly/article/details/125529931这是我的核心技术专栏,专栏中包含大量项目实战分析案例,详细讲解了问题的分析排查过程,有很强的实战参考价值,广受好评!专栏文章持续更新中!

3、最后

       本文详细介绍了引发C++软件异常的常见原因,并给出了详细的讲解和相应示例说明。希望大家在了解本文的内容之后,既能在编写代码时提前感知潜在的问题,也能在出问题之后多一些排查问题的思路与手段。

       在了解这些引发C++软件异常的常见原因之后,需要系统地掌握排查软件异常的常用方法,可以查看我的文章:

排查C++软件异常的常见思路与方法(实战经验总结)https://blog.youkuaiyun.com/chenlycly/article/details/120629327

C++软件调试与异常排查从入门到精通专栏介绍与文章汇总 根据近几年排查软件异常的实践与经验,系统地讲解了C++软件异常常见原因与常用排查方法,以图文并茂的方式给出具体的分析实例,带领大家逐步掌握C++软件异常排查的相关技术与要领。 阅读详情

相关推荐

【C++软件调试技术】C++软件开发维护过程中典型软件异常问题的排查与总结

本文以问答的方式进行展开,罗列了C++软件日常开发和维护中遇到的多个软件调试问题及有代表性的场景,给出详细的处置思路和处理办法,以供大家借鉴和参考。

dvlinker的技术专栏 17万+

排查C++软件异常的常见思路与方法(实战经验总结)

在多年排查C++软件异常实践的基础上,系统地总结了排查C++软件异常的常见思路与方法,有很强的实战参考价值!

dvlinker的技术专栏 7万+

C++软件调试与异常排查技术从入门到精通学习路线分享

详细讲述C++软件调试与异常排查技术从入门到精通学习路线。

dvlinker的技术专栏 6万+

第三方模块远程注入到C++软件中引发软件异常的若干实战案例分享

本文详细分享第三方模块远程注入到软件中引发软件异常的若干实战案例。

dvlinker的技术专栏 1万+

两例典型的C++软件异常排查实例分享

两例典型的C++软件异常崩溃问题详细分析

dvlinker的技术专栏 9495

C++程序使用 STL 容器发生异常的常见原因分析与总结

使用 STL 容器发生异常的常见原因分析与总结

dvlinker的技术专栏 5万+

引发C++程序内存错误的常见原因分析与总结

系统全面地总结引发C++程序内存错误的各种原因,以供参考。

dvlinker的技术专栏 2万+

C++软件异常分析概述

本文将详细讲述Windows系统中软件异常的分类以及常用的排查方法,给大家提供一个借鉴与参考。

dvlinker的技术专栏 4万+

【C++软件异常排查实战经验分享系列 ①】报错窗口点击重试 | 0xfeeefeee等异常值 | OutputDebugString | 数据断点 | if条件断点 | 历史版本比对法 | 汇编代码

在开发调试C++软件的过程中会遇到各式各样的问题,通过排查这些项目问题可以积累大量的实战排查经验和处理技巧,本文对这些经验和技巧做个总结和分享,以供大家借鉴或参考。

dvlinker的技术专栏 3万+

引发new/malloc动态申请内存失败的常见原因分析与总结

本文结合以往项目中遇到的多个问题实例,给大家详细总结一下引发动态申请内存失败的常见原因,以供借鉴或参考。

dvlinker的技术专栏 1万+

Ubuntu20.04 安装 MATLAB R2023b 常见问题及解决方案

本文提供了Ubuntu20.04系统上安装MATLAB R2023b的完整指南,包括系统要求检查、依赖项安装、详细安装步骤及常见问题解决方案。特别针对GTK模块加载失败、OpenGL图形加速问题和中文显示乱码等常见问题提供了有效解决方法,帮助用户顺利完成安装并优化使用体验。

weixin_42619061的博客 483

IFRS 9与CECL对比解析:跨国企业财务必须掌握的两种信用损失模型

本文深度解析IFRS 9与CECL两种信用损失模型的差异,帮助跨国企业应对财务合规挑战。从基础架构、数据治理到监管策略,详细对比三阶段模型与单一阶段模型的核心区别,并提供实施双轨制的实务建议,助力企业优化资本管理和税务筹划。

3c4x5z6v7b的博客 668

MTK-AWB 小结 (二)

调试总结:可以将offset 对应的ratio加大,保留更多的喜好色(偏色),影响WF statistic gain,从而影响最终的awb gain。这一部分前期CCT校准的时候就会算出一组默认值,当然这个offset offse 值用于优化T,S,wf 光下的偏色喜好度. offset。后面遇到 A H Shad 光收白或者偏色,还可以优化 A 的offset ,H offset,shad offset.new offset 计算过程(Day locus offset 和M/G offset )

qq_46056347的博客 1167

当拿到金葡菌的全基因组测序数据后该怎样进行分析呢

很多做科研实验/做课题报告的科研老师们在拿到全基因组测序数据之后要进行一系列的分析,但同时也担心数据庞大分析缓慢导致进度,也有因为不会编程不知道该如何进行拼接分析的...其实需要质控拼接、MLST分析及菌种鉴定等完全可以自己在平台上进行分析,下面以金葡菌为例进行了质控拼接及菌种鉴定等分析。 网络图片(侵删) 金黄色葡萄球菌(Staphylococcus aureus,S. aureus)也称“金葡菌”,隶属于葡萄球菌 属,是革兰氏阳性菌的代表,为一种常见的食源性致病微生物。 那么在取得了金葡菌全基

GenesClouds的博客 1969

Flash芯片的VCC上电到可操作时间过长

Flash的VCC上电到操作时间(tVSL)通常有最大值限制,如果超过这个时间,核心风险是状态机不同步和指令锁存错误。比如芯片内部振荡器可能已经尝试启动又休眠了,但主控却在这之后才发送指令,导致完全 misinterpreted。Flash芯片的(从电源上电到可以正常进行读写操作的时间)如果过长,会产生一系列负面影响简单来说,这个时间过长意味着电源上升速度太慢,违反了芯片的要求。

qq_61918649的博客 722

Docker小白也能搞定!5分钟用calibre-web搭建私人Kindle书库(附静读天下配置)

本文为零基础用户提供了一份详尽的指南,介绍如何利用Docker容器技术,在5分钟内快速部署Calibre-Web服务,从而搭建一个完全私有的个人Kindle书库。教程涵盖了从Docker安装、Calibre-Web一键部署、书籍元数据管理到移动端配置的全流程,并重点讲解了如何与安卓端知名阅读应用“静读天下”联动,实现跨设备无缝访问,打造媲美在线书城的个性化阅读体验。

weixin_29216957的博客 541

基于YOLOv8的铁路轨道异物检测系统设计与实现

目标检测是计算机视觉领域的核心任务之一,旨在识别图像中特定目标的位置和类别。传统方法主要依赖手工特征(如HOG、SIFT等)和分类器(如SVM),而深度学习方法通过卷积神经网络自动学习特征表示,显著提升了检测性能。YOLO(You Only Look Once)系列算法作为单阶段目标检测的代表,以其高效的检测速度著称。YOLOv8是Ultralytics公司于2023年发布的最新版本,在精度和速度上都有进一步提升。

YOLO 574

里海NX二次开发3000例-专栏目录

UDO(User Defined Object)是“用户自定义对象”。应用非常广,可以用UDO创建自定义特征,也可以对NX原有的特征添加一些特殊信息,这些信息会保存在部件中,以后可以查询。还可以实时追踪NX对象,例如做3D包容盒线框自动追踪实体,做2D矩形框自动追踪图纸尺寸值,这些在下面的例子都有介绍。读者可以自己编译下面的例子,体会理解UDO的用法。------里海 20230512序号内容​链接01使用UDO在所有实体上设置更新回调​链接02使用UDO生成跟踪实体的线框​链接03删除UDO​链接。

王牌飞行员_里海的博客 7714

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

上一篇: Visual Studio调试技巧与实用方法总结(实战经验分享)
下一篇: VC++分别使用WinExec、CreateProcess、ShellExecute和ShellExecuteEx来启动程序(附源码)
dvlinker
dvlinker 领域专家: C/C++技术领域 领域专家: C/C++技术领域
博客等级 码龄19年 13万+粉丝 1015原创
评论 175
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dvlinker

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值