再谈“0xC0000005读取写入位置冲突”——正确使用指针访问内存

本文通过一个测试程序探讨了在C语言中不当使用指针进行字符串操作时出现的0xC0000005异常。重点分析了指针指向字符串常量并尝试修改其内容时引发的访问冲突错误。

转自:http://www.typecodes.com/cseries/pointermemaccess0xC0000005.html

前面一篇文章中,总结了错误使用指针指向一个字符串常量,然后对该字符串常量进行写入操作导致出错:“未经处理的异常:0xC0000005: 读取/写入位置发生访问冲突”。这篇文章继续使用指针来探索这个问题。

1 测试程序:“异常: 0xC0000005: 写入位置 0xxxxxxxxx 时发生访问冲突”


下面是一个测试程序,主要是字符串的“连接”——strcat函数,malloc有迷惑性。其中,指针p1指向malloc在堆上开辟的连续内存块,数组p3是一种正确的字符连接方法。

/**
 * @FileName :  pointermemaccess0xC0000005.c
 * @Author   :  vfhky http://www.typecodes.com 2014.08.25 20:00
 * @Functions:  不正确使用指针p1导致出现“异常: 0xC0000005”和正确使用数组实现字符串的连接
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main( int argc, char * argv[] )
{
    char * p1 = (char *)malloc( sizeof(char)*20 );
    char p3[20]= "igkl";
    char * p2 = "efgh";
    p1 = "abcd";        //p1指向字符串"abcd"的首地址, 而不是把"abcd"拷贝到malloc开辟的内存块中
    strcat( p1, p2 );   //报错: 0xC0000005异常,访问冲突
    strcat( p3, p2 );   //正确
    printf( "p1=[%s], p2=[%s]\n", p1, p2 );
    printf( "p3=[%s], p2=[%s]\n", p3, p2 );

    //释放内存
    free( p1 );
    p1 = NULL;
    return 0;
}
</pre><pre name="code" class="cpp"> 
<h3><span style="font-family: Arial, Helvetica, sans-serif;">2 在vs中设置断点并调试</span></h3>如下图设置好断点后,单步执行到第10行。此时指针变量p1的值是 0x008734c0 ,p2的值是 0x00d75774 ,数组p3的起始地址是 0x0036f928 。
<img src="https://img-blog.youkuaiyun.com/20141205215314511?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvRmx5aW5nQmlyZF9TWEY=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />
<span style="font-family: Arial, Helvetica, sans-serif;">
</span>
2.1详细查看指针变量p1、p2以及数组p3的内存情况:(第10行)
此时,指针变量p1通过语句 char * p1 = (char *)malloc( sizeof(char)*20 ); 指向起始地址为 0x008734c0 到 地址 0x008734d3 的内存块(大小为 20 字节)。
<img src="https://img-blog.youkuaiyun.com/20141205215447003?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvRmx5aW5nQmlyZF9TWEY=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />
指针变量 p2 通过执行 char * p2 = "efgh"; 指向起始地址为 0x00d75774 的字符串“efgh”(共占 4 字节)。
<img src="https://img-blog.youkuaiyun.com/20141205215550296?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvRmx5aW5nQmlyZF9TWEY=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />
<span style="font-family: Arial, Helvetica, sans-serif;">
</span>
<span style="font-family: Arial, Helvetica, sans-serif;">数组 p3 通过 char p3[20]= "igkl"; 开辟了起始地址为 0x0036f928 的内存块(共占 4 字节),存储字符串“igkl”。</span>
<img src="https://img-blog.youkuaiyun.com/20141205215631773?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvRmx5aW5nQmlyZF9TWEY=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />

2.2继续单步调试并查看内存情况:(第11行)
上述变量初始化完毕后,单步执行到第 11 行的断点。这时指针变量p1由最初的指向 0x008734c0 变成指向字符串“abcd”的地址 0x00d7576c 。p2 的地址仍不变(0x00d75774),p3的地址也不变(0x0036f928)。
<img src="https://img-blog.youkuaiyun.com/20141205215813797?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvRmx5aW5nQmlyZF9TWEY=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />

详细查看指针变量p1的内存情况:起始地址是 0x00d7576c ,该内存块存储的字符串是“abcd”。


2.3继续单步调试并查看内存情况:(第12行)

然后,单步执行到第 12 行的断点,出现错误:test.exe 中的 0x0f85d2e9 (msvcr100d.dll) 处有未经处理的异常: 0xC0000005: 写入位置 0x00d75770 时发生访问冲突。也即在执行第11行的 strcat( p1, p2 ); 语句时,发生内存访问错误。该语句是把作为src的指针变量p2(0x00d75774)指向的内存块中的字符串“efgh”拷贝一份,然后复制到作为dst的指针变量p1(0x00d7576c)所指向内存块中的的字符串常量“abcd”后面。调用堆栈可以看到,当执行 msvcr100d.dll!strcat(unsigned char * dst, unsigned char * src) 对应的strcat.asm汇编文件中第178行语句 mov [edi], edx 时出错。



3 原因分析


其实,通过上面的单步调试,我们也已经知道原因了。现总结一下:因为指针变量p1(0x00d7576c)所指向的字符串常量“abcd”后面,也即字符'd'的地址是 0x00d7576c+3=0x00d7576f 。而它后面以地址 0x00d75770 开始的内存块是不属于指针变量p1的,没有访问权限,所以把拷贝自指针变量p2(0x00d75774)指向的字符串“efgh”到以 0x00d75770 为起始地址的内存块中会报错。可以把语句 p1 = "abcd"; 改成 p1 = p3;,然后直接使用 strcat( p1, p2 );,这样是合法的。


<think>我们面对的是一个访问冲突异常(错误代码0xC0000005),具体发生在尝试写入内存地址0x000000903B000FE8时。根据引用内容,这类错误通常由以下原因引起: 1. 访问未初始化或已释放的内存(如引用[1]提到0xfeeefeee是未初始化堆内存的标记) 2. 空指针解引用(如引用[4]提到读取位置0x00000000) 3. 内存越界访问(如引用[2]和引用[3]中的例子) 4. 释放后使用(Use After Free)或重复释放内存 5. 多线程同步问题 针对这个特定的写入地址0x000000903B000FE8,我们注意到这个地址看起来是一个正常的堆地址(以0x00000090开头,而非特殊的标记地址如0xfeeefeee或全零地址)。因此,问题可能指向: - 该内存区域已被释放,但程序仍尝试写入 - 多线程环境下,一个线程释放了内存而另一个线程仍在尝试写入 - 堆损坏(Heap Corruption)导致后续操作异常 ### 解决步骤 1. **确认错误发生位置** 使用调试器(如Visual Studio)在崩溃时查看调用堆栈(Call Stack),定位到引发异常的代码行。这是最关键的一步。 2. **检查指针有效性** 在崩溃点检查目标指针(即指向0x000000903B000FE8的指针)的来源: - 是否来源于动态内存分配(如`malloc`, `new`)? - 在写入前是否已被释放?可在分配和释放处设置断点或打印日志。 - 是否可能被其他线程修改?如果是多线程程序,检查同步机制(如互斥锁)。 3. **检查堆损坏** 堆损坏可能由以下原因引起: - 缓冲区溢出(写入超过分配大小的内存) - 释放后写入(Use After Free) - 重复释放(Double Free) 使用工具检测: - Windows平台可使用[Application Verifier](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/application-verifier)开启堆检查。 - 在调试器中启用页堆(PageHeap):`glandium.org`。 - 使用内存检测工具如Valgrind(Linux)或Dr. Memory(Windows)。 4. **检查第三方库** 如引用[2]和[3]中提到的OpenCV问题,检查是否因第三方库版本或调用方式导致。确保: - 库的Debug/Release版本使用正确(如引用[2]中Debug正常而Release崩溃)。 - 资源管理符合库要求(如图像加载后是否释放)。 5. **代码静态分析** 检查崩溃点附近的代码: - 指针是否可能为空? - 数组访问是否越界? - 是否有不匹配的内存分配/释放(如`new`/`delete[]`混用)? 6. **运行时防护** 在关键代码段添加防御性编程: ```c if (pointer == NULL) { // 处理空指针 } ``` 如引用[3]和[4]所建议。 ### 常见修复场景 - **场景1:访问已释放内存** 确保在释放对象后不再使用指针: ```c free(ptr); ptr = NULL; // 避免悬空指针 ``` - **场景2:多线程竞争** 使用同步机制保护共享资源: ```c std::mutex mtx; mtx.lock(); // 写入共享内存 mtx.unlock(); ``` - **场景3:堆缓冲区溢出** 检查写入操作是否超过分配大小: ```c char* buffer = (char*)malloc(10); // 错误:写入11字节导致溢出 strcpy(buffer, "12345678901"); ``` ### 工具推荐 1. **Visual Studio调试器**:启用“异常设置”捕获所有访问冲突。 2. **Dr. Memory**:检测内存错误(Windows)。 3. **AddressSanitizer**(VS2019+):编译时添加`/fsanitize=address`。 4. **DebugDiag**:分析崩溃转储文件。 > 如果上述步骤仍无法解决,请提供更多上下文(如崩溃时的调用堆栈、相关代码片段)以便进一步分析。 §§相关问题§§ 1. 如何配置Visual Studio捕获0xC0000005异常? 2. 在C++中如何避免悬空指针? 3. 如何调试多线程环境下的内存访问冲突? </think> ### 解决错误代码 0xC0000005写入位置 0x000000903B000FE8)的方法 这个访问冲突错误表明程序尝试**向无效的内存地址写入数据**。以下是系统性的解决方法,结合引用内容和常见修复实践: --- ### 1. **定位问题根源(关键步骤)** - **启用调试模式**: - 在 Visual Studio 中启用调试器,设置断点运行程序 - 当崩溃发生时,检查调用堆栈(Call Stack)定位触发异常的代码行 - 重点关注 `0x000000903B000FE8` 地址相关的指针操作 - **检查特殊内存标记**: - `0x000000903B000FE8` 的地址特征表明可能是**已释放的堆内存**(类似引用[1]中的 `0xfeeefeee`) - 使用内存断点(Memory Breakpoint)监控该地址的访问 --- ### 2. **常见原因与解决方案** #### (1)访问已释放的内存(Use-After-Free) ```c++ // 错误示例:指针释放后继续使用 int* ptr = new int(10); delete ptr; // 内存已释放 *ptr = 20; // 触发 0xC0000005 ``` **修复**: - 释放后立即置空指针: ```c++ delete ptr; ptr = nullptr; // 避免悬空指针 ``` - 使用智能指针(如 `std::unique_ptr`)自动管理内存 #### (2)未初始化的指针 ```c++ // 错误示例:指针未初始化即写入 int* ptr; // 未初始化 *ptr = 100; // 随机地址写入 -> 访问冲突 ``` **修复**: - 确保指针有效后再操作: ```c++ int* ptr = new int; // 分配内存 *ptr = 100; ``` #### (3)多线程竞争 - 线程A释放内存后,线程B尝试写入(引用[2]的典型场景) **修复**: - 使用互斥锁保护共享资源: ```c++ std::mutex mtx; mtx.lock(); *shared_ptr = value; // 安全写入 mtx.unlock(); ``` #### (4)堆内存损坏(Heap Corruption) - 缓冲区溢出或重复释放导致堆结构破坏 **检测工具**: - Visual Studio 启用[页堆 (PageHeap)](https://learnstudio.github.io/):`gflags.exe /p /enable YourApp.exe` - 使用 Application Verifier 检测内存问题 --- ### 3. **调试技巧** 1. **检查指针有效性**: ```c++ if (ptr == nullptr || ptr == (void*)0xfeeefeee) { // 处理无效指针(引用[1][4]) } ``` 2. **启用安全机制**: - 编译选项:`/RTCsu`(启用运行时检查) - 使用 `try/catch` 捕获结构化异常: ```cpp __try { /* 可疑代码 */ } __except (EXCEPTION_EXECUTE_HANDLER) { /* 处理访问冲突 */ } ``` 3. **分析内存转储**: - 通过 `.dmp` 文件在 WinDbg 中运行命令: ``` !analyze -v !heap -p -a 0x000000903B000FE8 ``` --- ### 4. 第三方库问题(如引用[2][3]的 OpenCV) - **检查库版本一致性**: - Debug/Release 模式必须使用对应版本的库(如 `opencv_world320d.dll` 仅用于 Debug) - 确保资源生命周期正确(如 `cv::Mat` 释放后不再使用) - **添加空指针保护**(引用[3]): ```c++ cv::Mat img = cv::imread("path.jpg"); if (img.empty()) { // 必须检查 std::cerr << "加载图像失败"; return; } ``` --- > **总结流程**: > 启用调试 → 定位崩溃点 → 检查指针生命周期 → 验证多线程同步 → 使用内存检测工具 > 多数情况下,该错误由**悬空指针**或**堆损坏**导致,需结合具体代码分析[^1][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值