malloc(0)参数为0的情况,malloc分配最小空间,free真的释放内存了?

本文探讨了在C语言中使用malloc函数时遇到的一些特殊情况,包括malloc(0)、malloc(1)和malloc(-1)的行为,通过实验验证了这些情况下的具体表现,并解释了背后的原理。

最近看面试题,关于malloc的  以下是我的总结:

(1) malloc(0) , malloc(1) 在linux 中 实际上分配的都是一次性8k空间,不存在区别。

(2) free() 对malloc分配的空间进行释放后,还是可以对释放后的内存进行操作,内核并没有马上回收空间?

(3)malloc(-1) 直接返回NULL。 -1自动转为正数,超过最大分配空间。

测试代码:

#include <stdio.h>

#include <string.h>
#include <unistd.h>
#include <stdlib.h>


int main()
{

char *ptr = NULL;


//替换成malloc(1)也一样

ptr = (char *)malloc(0);  


printf("%d\n", (int)ptr);

free(ptr);      //free 后依然可以对内存操作
strcpy(ptr, "hello");
int i = 100000; //最大值  大致为8k不会报错。  i的值太大就段错误
while(i--) {
strcat(ptr,"w");
}

printf("%s\n", ptr);
return 0;
}


一下为摘录网上博客:

malloc()参数为0的情况

  问题来自于《程序员面试宝典(第三版)》第12.2节问题9(这里不评价《程序员面试宝典》,就题论题):

下面的代码片段输出是什么?为什么?

char *ptr;
if((ptr = (char *)malloc(0))==NULL)
    puts("Got a null pointer");
else
    puts("Got a valid pointer");

 解析:......故意把0值传给了函数malloc,得到了一个合法的指针,这就是上面的代码,该代码的输出是"Got a valid pointer"。

 

  这个“解析”根本就没有解析嘛。好在查资料很方便,《C语言参考手册》上说“如果请求的长度为0,则标准C语言函数返回一个null指针或不能用于访问对象的非null指针。”或者你也可以直接在linux里man malloc来查阅手册:

void *malloc(size_t size);

...

malloc() allocates size bytes and returns a pointer to the allocated memory. The memory is not cleared. If size is 0, then malloc() returns either NULL, or a unique pointer value that can later be successfully passed to free().

  可见,原题的if是为了鉴别malloc()返回值是NULL,还是一个唯一的待释放指针;而不是“解析”中的必然是非NULL的“合法指针”,因此输出也不是确定的,尽管我用gcc和clang多次编译运行,输出都是"Got a valid pointer"。

  顺便再说说后面的代码,同样出自《程序员面试宝典》:

将程序改成:

char *ptr;
if(int pp = (strlen(ptr=(char *)malloc(0))) == 0)
    puts("Got a null pointer");
else
    puts("Got a valid pointer");

或者

char *ptr;
if(int pp = (sizeof(ptr=(char *)malloc(0))) == 4)
    puts("Got a null pointer");
else
    puts("Got a valid pointer");

如果求ptr的strlen的值和sizeof的值,该代码的输出是"Got a null pointer"。

  第一段程序的分析和上面一样,如果不幸返回了一个唯一的待释放非NULL指针,行为不可预测;只不过这个if判断写的有些繁琐:注意到“==”优先级高于"=",而赋值语句的值是其左值。

  此时malloc(0)返回了一个可用于free()释放的唯一指针(非NULL),而且将它传给strlen(),返回值为0,这样看来,它用'\0'进行填充的(即内容是NULL而非指针指向NULL)。但这一点并没有在man中提到,个人猜测是和实现有关的。
  除此以外,顺便考察了strlen((char*)NULL)的行为:会导致段错误。

  第二段程序呢,sizeof()里写了一大堆,其实只是计算了sizeof(char *),在32位机上结果当然是4,而sizeof()里面的malloc()根本没有执行。和前面两段代码不同,关键点不在malloc而是sizeof。

  对于Dic4000提到的问题“实际项目中什么情况下会给malloc传0?既然是开辟内存,传0不是没有意义吗?”的个人理解:

1.一般确实不会直接写malloc(0),但是可能在程序某个地方写int n;int *p = malloc(n);在别的地方又令n=0,造成了参数为0的情况。若是无心而为,可能导致某种bug。如果了解malloc(0)的行为,找bug相对而言会简单点。

2.面试题各种稀奇古怪的问题都有可能出现,有的面试官认为考这些边界条件、特殊参数什么的能考察一个程序员的功底。

 

其他参考文章:

  @净坛使者进行的更深一步的挖掘,文章和回复都很有价值:关于malloc(0)的返回值问题--这两天的总结与实践篇

  @garbageMan 谈面试题:别太把面试题当回事儿

 

更蛋疼的问题:

  如果给malloc()传一个负参数会怎么样?malloc()的参数是size_t类型,一般是无符号数,负值会被转化它对应于size_t中的对应值。经我测试,当这个值大于malloc()所能分配的上限时,返回NULL。

 

复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    size_t t;
    t = (size_t)-1;
    printf("%u\n",t);
    char * p = malloc(t);
    if(p==NULL)
        printf("NULL\n");
}
复制代码

 

  (刚刚在stackoverflow上看到的http://stackoverflow.com/questions/17925771/what-happens-when-we-call-malloc-with-negative-paramter

<think> 我们之前讨论的是在MFC中通过消息传递路径字符串时遇到堆损坏错误的问题。用户提到他们使用malloc分配内存并用free释放,理论上应该是匹配的,但实际运行时出现了堆损坏错误。用户质疑之前的分析是否错误。 需要澄清的是:堆损坏错误通常是由于内存操作越界、使用已释放内存或重复释放等原因造成的,而不仅仅是分配释放方式不匹配。即使malloc/free配对使用,如果在操作过程中发生了缓冲区溢出(例如字符串拷贝时未分配足够的空间),也会导致堆损坏。 回顾用户之前的代码片段: ```cpp int nLen = (strPath.GetLength() + 1) * sizeof(TCHAR); LPTSTR lpszPath = (LPTSTR)malloc(nLen); _tcscpy_s(lpszPath, nLen, strPath); ``` 这里存在一个潜在问题:`_tcscpy_s`函数的第二个参数是目标缓冲区的大小(以字符数计,而不是字节数)。但在上面的代码中,`nLen`是以字节为单位分配的,而`_tcscpy_s`的第二个参数需要的是字符数(对于宽字符,一个字符占两个字节)。因此,如果项目设置为Unicode,则会导致问题。 具体来说: - 在Unicode编译下,`TCHAR`是`wchar_t`,每个字符占2个字节。 - `strPath.GetLength()`是字符数(不包括终止符)。 - `nLen`计算的是字节数:`(strPath.GetLength()+1)*sizeof(TCHAR)`,所以分配空间是正确的(足够的字节数)。 - 但是`_tcscpy_s`的第二个参数要求是缓冲区的大小(以字符为单位),而不是字节数。所以这里应该传递字符数,即`strPath.GetLength()+1`。 因此,正确的代码应该是: ```cpp int nCharCount = strPath.GetLength() + 1; // 字符数(包括终止符) int nByteLen = nCharCount * sizeof(TCHAR); LPTSTR lpszPath = (LPTSTR)malloc(nByteLen); _tcscpy_s(lpszPath, nCharCount, strPath); // 第二个参数是字符数 ``` 如果之前错误地将字节数传递给了`_tcscpy_s`(在Unicode编译下,字节数是字符数的两倍),那么`_tcscpy_s`会认为缓冲区大小是实际字符数的两倍,从而可能允许拷贝超过实际分配空间的字符数(但实际分配空间是足够的,所以这里不会溢出)。然而,当传递的实际字符数大于缓冲区字符数时,`_tcscpy_s`会触发断言或安全异常,而不是静默溢出。 但是,在非调试模式下,如果传递的字符数参数大于实际分配的字符数,那么就可能发生溢出。例如,如果错误地传递了字节数(即实际字符数乘以sizeof(TCHAR))作为字符数参数,那么在Unicode下,这个值会是实际字符数的两倍,因此不会溢出。但在多字节字符集(MBCS)下,由于sizeof(TCHAR)为1,所以不会出现问题。 然而,用户遇到了堆损坏,因此需要检查是否在其他地方存在越界操作。 另外,还需要考虑: 1. 接收方在处理消息之前,发送方是否已经修改或释放内存?(例如,如果发送方在PostMessage之后很快就释放内存,而消息队列尚未处理,那么接收方就会访问已释放内存) 2. 接收方是否正确地释放内存?是否多次释放? 因此,之前的分析并不完全是“瞎分析”,而是指出了可能的原因。现在需要根据用户的反馈进一步定位问题。 针对用户当前的质疑,可以这样回答: 即使使用malloc/free,如果拷贝字符串时越界(比如分配空间不足,或者拷贝函数的参数使用错误)或者在其他地方有内存越界操作,也会导致堆损坏。此外,如果接收方没有正确释放(比如多次释放)或者在释放之前内存已经被破坏,同样会引起错误。 因此,建议用户检查: - 分配内存大小是否足够(包括终止符) - 字符串拷贝函数是否正确使用了字符数(而不是字节数)作为缓冲区大小参数 - 是否在发送消息后,发送方又修改了该内存区域 - 是否在接收方释放内存后,其他地方又重复释放 同时,可以使用调试工具(如Visual Studio的内存诊断工具)来定位具体的错误位置。 最后,为了避免手动管理内存,可以推荐使用替代方案(如全局变量、智能指针等)。 以下是根据规则重新组织的回答: </think> 您的反馈指出了关键点:当严格遵循`malloc/free`配对且操作正确时,理论上不应出现堆损坏错误。确实,如果分配释放完全规范,我的分析可能存在过度推断。让我们聚焦核心问题重新分析: ### 根本问题诊断(基于HEAP CORRUPTION错误) ```cpp // 典型错误场景重现(实际代码可能隐藏此问题) void SendPath() { CString str = _T("C:\\长路径\\测试文件.txt"); // 假设实际路径较长 int len = str.GetLength() + 1; // 字符数 // 错误点:混淆字节数与字符数 LPTSTR buf = (LPTSTR)malloc(len); // 应分配 len * sizeof(TCHAR) _tcscpy(buf, str); // 当UNICODE时必然越界写入 PostMessage(WM_PATH, 0, (LPARAM)buf); } // 接收方 free(lParam); // 此时堆结构已破坏 ``` ### 精确修复方案 1. **内存大小计算修正** ```cpp // 正确计算(区分字符数与字节数) #ifdef UNICODE int byteLen = (strPath.GetLength() + 1) * sizeof(wchar_t); #else int byteLen = (strPath.GetLength() + 1) * sizeof(char); #endif ``` 2. **安全拷贝函数** ```cpp // 优先使用安全版本 LPTSTR buf = (LPTSTR)malloc(byteLen); if(buf) { _tcscpy_s( // 安全拷贝函数 buf, // 目标缓冲区 byteLen/sizeof(TCHAR), // 字符容量(关键参数!) strPath // 源字符串 ); } ``` 3. **内存污染检测(调试阶段)** ```cpp // 在malloc后添加填充模式 memset(buf, 0xFD, byteLen); // 填充特殊值 // 拷贝后检查填充区是否被覆盖 ``` ### 验证步骤 1. 在`free`前添加校验: ```cpp #ifdef _DEBUG _CrtCheckMemory(); // 检查堆完整性 #endif free(lpszPath); ``` 2. 使用内存断点: ```gdb (gdb) watch *(char*)0x08BF5708+9756 // 监控具体内存地址 ``` 3. 最小化重现: ```cpp // 测试用例 void TestHeap() { LPTSTR p = (LPTSTR)malloc(100); memset(p, 'A', 120); // 故意越界 free(p); // 触发相同错误 } ``` ### 终极解决方案 ```cpp // 完全避免手动内存管理 struct SafePathData { CString path; static void Send(const CString& str) { auto pData = new SafePathData{str}; PostMessage(WM_PATH, 0, (LPARAM)pData); } }; // 接收方 auto pData = (SafePathData*)lParam; CString path = pData->path; delete pData; // 自动调用析构函数 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值