【整理】为什么在C/C++中总是对malloc的返回值进行强制转换

简介:
============= 文章1 ================  

       首先要说的是,使用 malloc 函数,请包含 stdlib.h(C++ 中可以是 cstdlib),而不是 malloc.h 。因为 malloc.h 从来没有在 C 或者 C++ 标准中出现过!因此并非所有编译器都有 malloc.h 这个头文件。但是所有的 C 编译器都应该有 stdlib.h 这个头文件。   

在 C++ 中,强制转换 malloc() 的返回值是必须的,否则不能通过编译。  
在 C 中,这种强制转换却是多余的,并且不利于代码维护。   

       起初,C 没有 void* 指针,那时 char* 被用作泛型指针(generic pointer),所以那时  malloc 的返回值是 char* 。因此,那时必须强制转换 malloc 的返回值。后来,ISO C 标准定义了 void* 指针作为新的泛型指针。并且 void* 指针可以不经转换,直接赋值给任何类型的指针(函数指针除外)。从此,malloc 的返回值变成了 void* 之后,便不再需要强制转换 malloc 的返回值了。以下程序在 VC6 编译无误通过。 
 
?
1
2
3
4
5
6
#include <stdlib.h>
int main( void )
{
     double *p = malloc ( sizeof *p ); /* 不推荐用 sizeof( double ) */
     return 0;
}
       当然,强制转换 malloc 的返回值并没有错,但画蛇添足!例如,日后你有可能把 double *p 改成 int *p 。这时,你就要把所有相关的 (double *)malloc( sizeof(double) ) 改成 (int *)malloc( sizeof(int) ) 。如果改漏了,那么你的程序就会存在 bug 。就算你有把握把所有相关的语句都改掉,但这种无聊乏味的工作你不会喜欢吧!不使用强制转换可以避免这样的问题,而且书写简便,何乐而不为呢?使用以下代码,无论以后指针改成什么类型,都不用作任何修改。   
?
1
double *p = malloc ( sizeof *p );
       值得一提的是,上述写法中 p 虽然没有指向具体的内存空间,但并不影响通过 sizeof *p 来计算占用内存的大小。  
       类似地,使用 calloc ,realloc 等返回值为 void* 的函数时,也不需要强制转换返回值。  


============= 文章2 ================  

      本文概括叙述了上文的内容,并且针对 malloc 返回值的 3 种转型方式进行总结,(相对于上文)更全面的总结其各自的应用范围。  

以前有篇文章叫《C/C++ 误区 —— 强制转换 malloc() 的返回值》(即上文)。文章大致内容是:  
  • malloc 函数在 <stdlib.h> 或者 <cstdlib> 头文件中,而不是 <malloc.h> 中。
  • 由于 C 语言最初没有 void 类型,所以是使用 char* 来代表通用指针。
?
1
2
3
4
5
6
7
8
9
10
11
/* the old declaration of malloc */
char * malloc ( size_t size);
 
char * p = malloc ( size * sizeof (*p) );
/* 可以,不需要转型 */
 
T1* p1 = malloc (size1 * sizeof (*p1) );
/* (T1!=char) 不可以,char*不能隐式转换成T1*  */
 
T2* p2 = (T2*) malloc (size2 * sizeof (*p2) );
/* (T2!=char) 可以,显示类型转换 */
  • C 语言后来引入了 void 类型,开始使用 void* 代表通用指针,同时规定 void* 可以隐式转换到任意指针类型。 
?
1
2
3
4
5
6
7
8
9
10
11
/* the new declaration of malloc */
void * malloc ( size_t size);
 
char * p = malloc ( size * sizeof (*p) );
/* 仍然可以,void* 可以隐式转换到任意指针类型 */
 
T1* p1 = malloc ( size1 * sizeof (*p1) );
/* 现在可以,void* 可以隐式转换到任意指针类型 */
 
T2* p2 = (T1*) malloc ( size2 * sizeof (*p2) );
/* 仍然可以,但不再必须 */
  • 在引入了 void 之后的 C 语言中,再使用强制转换是画蛇添足,同时影响代码维护,并且说这是一个 C/C++ 的误区。  
上面 4 点属于原文观点,下面阐述本文观点。  

对 malloc 返回值的转型,大致有以下三种方式:   
  • 仅在 C 中
/* legal only in C */  
?
1
2
/* 新头文件,具有 void 类型 */
T* p = malloc (size * sizeof (*p) ); /* T!=void */
?
1
2
/* 旧头文件,不具有 void 类型 */
T* p = (T*) malloc (size* sizeof (*p) ); /* T!=void */
  • 仅在 C++ 中
       C++ 天然支持 void ,但是不允许 void* 隐式转换到任意类型指针,需要 static_cast 。  

// legal only in C++  
?
1
2
// 新头文件
T* p = static_cast <T*>( malloc (size * sizeof (*p) ));
?
1
2
// 旧头文件(目前还有这种编译器吗)
T* p = reinterpret_cast <T*>( malloc (size * sizeof (*p) ));
?
1
2
3
4
5
// 当然在 C++ 中应该考虑
T* p = new T[size];
// 或者
std::vector<T> p(size);
// 但这不是文章讨论重点
  • 在 C/C++ 中
?
1
2
3
/* legal in both C and C++ */
/* legal in both new  and old header */
T* p = (T*) malloc (size * sizeof (*p) );
       第 1 种对新头文件的转型方式,如同代码第 1 行所说,仅在 C 编译器中合法。因为 C++ 不支持 void* 到其他指针类型的隐式转换。所以,原文章说这是 C/C++ 的误区,并不准确。这仅仅是(引入 void 类型之后的)C 语言中的“非必须”的动作,是否是误区,还有待考量。  
       第 2 种对新旧头文件的转型方式,代码第 1 行也说了,仅在 C++ 编译器中合法。因为 C 编译器不认识 static_cast 或者 reinterpret_cast 。  
       第3种,是一种中庸的写法。如同代码第 1 行所说:此代码无论是在 C 还是 C++ 编译器,无论是新头文件还是旧头文件,都是合法的代码。是可移植性最好的代码。   因为代码中使用的(C 风格的)转型、malloc,C/C++ 都支持。  
       所以,这种写法并不一定是误区或者画蛇添足。因为代码的作者也许比原文章的作者对移植性(C 和 C++ 的新旧编译器)考虑更多。  

参考资料:ISO/IEC 9899:1999 (E) Programming languages — C 7.20.3.3 The malloc function  
目录
相关文章
|
4月前
|
存储 编译器 C语言
【C/C++ 函数返回的奥秘】深入探究C/C++函数返回:编译器如何处理返回值
【C/C++ 函数返回的奥秘】深入探究C/C++函数返回:编译器如何处理返回值
488 3
|
4月前
|
JavaScript 编译器 API
【C++ 函数和过程 进阶篇】全面掌握C++函数返回值:从入门到精通的实战指南
【C++ 函数和过程 进阶篇】全面掌握C++函数返回值:从入门到精通的实战指南
153 1
|
4月前
|
API 数据库 C语言
【C/C++ 数据库 sqlite3】SQLite C语言API返回值深入解析
【C/C++ 数据库 sqlite3】SQLite C语言API返回值深入解析
228 0
|
4月前
|
安全 算法 C++
【C++泛型编程 进阶篇】模板返回值的优雅处理(二)
【C++泛型编程 进阶篇】模板返回值的优雅处理
114 0
|
4月前
|
安全 算法 编译器
【C++泛型编程 进阶篇】模板返回值的优雅处理(一)
【C++泛型编程 进阶篇】模板返回值的优雅处理
117 0
|
2月前
|
存储 编译器 程序员
【C/C++】动态内存管理(C:malloc,realloc,calloc,free)
探索C++与C语言的动态内存管理:从malloc到new/delete,了解内存分布及栈、堆的区别。文章涵盖malloc、realloc、calloc与free在C中的使用,强调内存泄漏的风险。C++引入new和delete,支持对象构造与析构,还包括operator new和placement-new。深入分析内存管理机制,揭示C与C++在内存处理上的异同。别忘了,正确释放内存至关重要!
|
3月前
|
编译器 C++ 开发者
C++一分钟之-返回值优化与Move Semantics
【6月更文挑战第19天】C++的RVO与移动语义提升效率,减少对象复制。RVO是编译器优化,避免临时对象的创建。移动语义通过右值引用和`std::move`转移资源所有权。注意RVO不是总有效,不应过度依赖。使用移动语义时,避免误用`std::move`导致对象无效。示例展示了RVO和移动构造函数的应用。理解并恰当使用这些机制能写出更高效代码。
42 3
|
4月前
|
存储 算法 编译器
【C++ 函数 基础教程 第四篇】深入C++函数返回值:理解并优化其性能
【C++ 函数 基础教程 第四篇】深入C++函数返回值:理解并优化其性能
362 1
|
4月前
|
存储 程序员 Shell
【C/C++ 内存管理函数】C语言动态内存管理大揭秘:malloc、calloc、realloc与new的对比与差异
【C/C++ 内存管理函数】C语言动态内存管理大揭秘:malloc、calloc、realloc与new的对比与差异
207 0
|
4月前
|
定位技术 C++ Python
C++一个函数返回两个或更多个返回值的方法
C++一个函数返回两个或更多个返回值的方法