热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

cmalloc头文件_软件特攻队|C/C++程序员的自我修养(2)

什么是好的程序员?是不是懂得很多技术细节?还是懂底层编程?还是编程速度比较快?我觉得都不是。对于一些技术细节来说和底层的技术
f842b05c9551c33250ea42cb6f2251bf.png

什么是好的程序员?是不是懂得很多技术细节?还是懂底层编程?还是编程速度比较快?我觉得都不是。对于一些技术细节来说和底层的技术,只要看帮助,查资料就能找到,对于速度快,只要编得多也就熟能生巧了。

如果要了解一个程序员,我想首先最想看的就是他的程序代码,程序代码可以看出一个程序员的素质和修养,程序就像一个作品,有素质有修养的程序员的作品必然是一图精美的图画,一首美妙的歌曲,一本赏心悦目的小说。

“细微之处见真功”,真正能体现一个程序的功底恰恰在这些细微之处。

这就是程序员的——编程修养。我总结了在用C/C++语言(主要是C语言)进行程序写作上的三十二个“修养”,通过这些,你可以写出质量高的程序,同时也会让看你程序的人渍渍称道,那些看过你程序的人一定会说:“这个人的编程修养不错”。

e6822d20e0668066b4787a9daedc6ded.png

6、if 语句对出错的处理

我看见你说了,这有什么好说的。还是先看一段程序代码吧。

if ( ch >&#61; &#39;0&#39; && ch <&#61; &#39;9&#39; ){
/* 正常处理代码 */
}else{
/* 输出错误信息 */
printf("error ......n");
return ( FALSE );
}

这种结构很不好&#xff0c;特别是如果“正常处理代码”很长时&#xff0c;对于这种情况&#xff0c;最好不要用else。先判断错误&#xff0c;如&#xff1a;

if ( ch <&#39;0&#39; || ch > &#39;9&#39; ){
/* 输出错误信息 */
printf("error ......n");
return ( FALSE );
}
/* 正常处理代码 */
......

这样的结构&#xff0c;不是很清楚吗&#xff1f;突出了错误的条件&#xff0c;让别人在使用你的函数的时候&#xff0c;第一眼就能看到不合法的条件&#xff0c;于是就会更下意识的避免。

7、头文件中的#ifndef

千万不要忽略了头件的中的#ifndef&#xff0c;这是一个很关键的东西。比如你有两个C文件&#xff0c;这两个C文件都include了同一个头文件。而编译时&#xff0c;这两个C文件要一同编译成一个可运行文件&#xff0c;于是问题来了&#xff0c;大量的声明冲突。

还是把头文件的内容都放在#ifndef和#endif中吧。不管你的头文件会不会被多个文件引用&#xff0c;你都要加上这个。一般格式是这样的&#xff1a;

#ifndef <标识>
#define <标识>
......
......
#endif

<标识>在理论上来说可以是自由命名的&#xff0c;但每个头文件的这个“标识”都应该是唯一的。标识的命名规则一般是头文件名全大写&#xff0c;前后加下划线&#xff0c;并把文件名中的“.”也变成下划线&#xff0c;如&#xff1a;stdio.h

#ifndef _STDIO_H_
#define _STDIO_H_
......
#endif

&#xff08;BTW&#xff1a;预编译有多很有用的功能。你会用预编译吗&#xff1f;&#xff09;

8、在堆上分配内存

可能许多人对内存分配上的“栈 stack”和“堆 heap”还不是很明白。包括一些科班出身的人也不明白这两个概念。我不想过多的说这两个东西。简单的来讲&#xff0c;stack上分配的内存系统自动释放&#xff0c;heap上分配的内存&#xff0c;系统不释放&#xff0c;哪怕程序退出&#xff0c;那一块内存还是在那里。stack一般是静态分配内存&#xff0c;heap上一般是动态分配内存。

由malloc系统函数分配的内存就是从堆上分配内存。从堆上分配的内存一定要自己释放。用free释放&#xff0c;不然就是术语——“内存泄露”&#xff08;或是“内存漏洞”&#xff09;—— Memory Leak。于是&#xff0c;系统的可分配内存会随malloc越来越少&#xff0c;直到系统崩溃。还是来看看“栈内存”和“堆内存”的差别吧。

栈内存分配 —————

char *AllocStrFromStack()
{
char pstr[100];
return pstr;
}

堆内存分配 ————

char *AllocStrFromHeap(int len)
{
char *pstr;
if ( len <&#61; 0 ) return NULL;
return ( char* ) malloc( len );
}

对于第一个函数&#xff0c;那块pstr的内存在函数返回时就被系统释放了。于是所返回的char*什么也没有。而对于第二个函数&#xff0c;是从堆上分配内存&#xff0c;所以哪怕是程序退出时&#xff0c;也不释放&#xff0c;所以第二个函数的返回的内存没有问题&#xff0c;可以被使用。但一定要调用free释放&#xff0c;不然就是Memory Leak&#xff01;

在堆上分配内存很容易造成内存泄漏&#xff0c;这是C/C&#43;&#43;的最大的“克星”&#xff0c;如果你的程序要稳定&#xff0c;那么就不要出现Memory Leak。所以&#xff0c;我还是要在这里千叮咛万嘱付&#xff0c;在使用malloc系统函数&#xff08;包括calloc&#xff0c;realloc&#xff09;时千万要小心。

记得有一个UNIX上的服务应用程序&#xff0c;大约有几百的C文件编译而成&#xff0c;运行测试良好&#xff0c;等使用时&#xff0c;每隔三个月系统就是down一次&#xff0c;搞得许多人焦头烂额&#xff0c;查不出问题所在。只好&#xff0c;每隔两个月人工手动重启系统一次。出现这种问题就是Memery Leak在做怪了&#xff0c;在C/C&#43;&#43;中这种问题总是会发生&#xff0c;所以你一定要小心。一个Rational的检测工作——Purify&#xff0c;可以帮你测试你的程序有没有内存泄漏。

我保证&#xff0c;做过许多C/C&#43;&#43;的工程的程序员&#xff0c;都会对malloc或是new有些感冒。当你什么时候在使用malloc和new时&#xff0c;有一种轻度的紧张和惶恐的感觉时&#xff0c;你就具备了这方面的修养了。

对于malloc和free的操作有以下规则&#xff1a;

  • 配对使用&#xff0c;有一个malloc&#xff0c;就应该有一个free。&#xff08;C&#43;&#43;中对应为new和delete&#xff09;
  • 尽量在同一层上使用&#xff0c;不要像上面那种&#xff0c;malloc在函数中&#xff0c;而free在函数外。最好在同一调用层上使用这两个函数。
  • malloc分配的内存一定要初始化。free后的指针一定要设置为NULL。

注&#xff1a;虽然现在的操作系统&#xff08;如&#xff1a;UNIX和Win2k/NT&#xff09;都有进程内存跟踪机制&#xff0c;也就是如果你有没有释放的内存&#xff0c;操作系统会帮你释放。但操作系统依然不会释放你程序中所有产生了Memory Leak的内存&#xff0c;所以&#xff0c;最好还是你自己来做这个工作。&#xff08;有的时候不知不觉就出现Memory Leak了&#xff0c;而且在几百万行的代码中找无异于海底捞针&#xff0c;Rational有一个工具叫Purify&#xff0c;可能很好的帮你检查程序中的Memory Leak&#xff09;

a57c9f1aea930691b7346d77319dc177.png

9、变量的初始化

接上一条&#xff0c;变量一定要被初始化再使用。C/C&#43;&#43;编译器在这个方面不会像JAVA一样帮你初始化&#xff0c;这一切都需要你自己来&#xff0c;如果你使用了没有初始化的变量&#xff0c;结果未知。好的程序员从来都会在使用变量前初始化变量的。如&#xff1a;

  • 对malloc分配的内存进行memset清零操作。&#xff08;可以使用calloc分配一块全零的内存&#xff09;
  • 对一些栈上分配的struct或数组进行初始化。&#xff08;最好也是清零&#xff09;

不过话又说回来了&#xff0c;初始化也会造成系统运行时间有一定的开销&#xff0c;所以&#xff0c;也不要对所有的变量做初始化&#xff0c;这个也没有意义。好的程序员知道哪些变量需要初始化&#xff0c;哪些则不需要。如&#xff1a;以下这种情况&#xff0c;则不需要。

char *pstr; /* 一个字符串 */
pstr &#61; ( char* ) malloc( 50 );
if ( pstr &#61;&#61; NULL ) exit(0);
strcpy( pstr, "Hello Wrold" );

但如果是下面一种情况&#xff0c;最好进行内存初始化。&#xff08;指针是一个危险的东西&#xff0c;一定要初始化&#xff09;

char **pstr; /* 一个字符串数组 */
pstr &#61; ( char** ) malloc( 50 );
if ( pstr &#61;&#61; NULL ) exit(0);
/* 让数组中的指针都指向NULL */
memset( pstr, 0, 50*sizeof(char*) );

而对于全局变量&#xff0c;和静态变量&#xff0c;一定要声明时就初始化。因为你不知道它第一次会在哪里被使用。所以使用前初始这些变量是比较不现实的&#xff0c;一定要在声明时就初始化它们。如&#xff1a;

Links *plnk &#61; NULL; /* 对于全局变量plnk初始化为NULL */

10、h和c文件的使用

H文件和C文件怎么用呢&#xff1f;一般来说&#xff0c;H文件中是declare&#xff08;声明&#xff09;&#xff0c;C文件中是define&#xff08;定义&#xff09;。因为C文件要编译成库文件&#xff08;Windows下是.obj/.lib&#xff0c;UNIX下是.o/.a&#xff09;&#xff0c;如果别人要使用你的函数&#xff0c;那么就要引用你的H文件&#xff0c;所以&#xff0c;H文件中一般是变量、宏定义、枚举、结构和函数接口的声明&#xff0c;就像一个接口说明文件一样。而C文件则是实现细节。

H文件和C文件最大的用处就是声明和实现分开。这个特性应该是公认的了&#xff0c;但我仍然看到有些人喜欢把函数写在H文件中&#xff0c;这种习惯很不好。&#xff08;如果是C&#43;&#43;话&#xff0c;对于其模板函数&#xff0c;在VC中只有把实现和声明都写在一个文件中&#xff0c;因为VC不支持export关键字&#xff09;。而且&#xff0c;如果在H文件中写上函数的实现&#xff0c;你还得在makefile中把头文件的依赖关系也加上去&#xff0c;这个就会让你的makefile很不规范。

最后&#xff0c;有一个最需要注意的地方就是&#xff1a;带初始化的全局变量不要放在H文件中&#xff01;

例如有一个处理错误信息的结构&#xff1a;

char* errmsg[] &#61; {
/* 0 */ "No error",
/* 1 */ "Open file error",
/* 2 */ "Failed in sending/receiving a message",
/* 3 */ "Bad arguments",
/* 4 */ "Memeroy is not enough",
/* 5 */ "Service is down; try later",
/* 6 */ "Unknow information",
/* 7 */ "A socket operation has failed",
/* 8 */ "Permission denied",
/* 9 */ "Bad configuration file format",
/* 10 */ "Communication time out",
......
......
};

请不要把这个东西放在头文件中&#xff0c;因为如果你的这个头文件被5个函数库&#xff08;.lib或是.a&#xff09;所用到&#xff0c;于是他就被链接在这5个.lib或.a中&#xff0c;而如果你的一个程序用到了这5个函数库中的函数&#xff0c;并且这些函数都用到了这个出错信息数组。那么这份信息将有5个副本存在于你的执行文件中。如果你的这个errmsg很大的话&#xff0c;而且你用到的函数库更多的话&#xff0c;你的执行文件也会变得很大。

正确的写法应该把它写到C文件中&#xff0c;然后在各个需要用到errmsg的C文件头上加上 extern char* errmsg[]; 的外部声明&#xff0c;让编译器在链接时才去管他&#xff0c;这样一来&#xff0c;就只会有一个errmsg存在于执行文件中&#xff0c;而且&#xff0c;这样做很利于封装。

我曾遇到过的最疯狂的事&#xff0c;就是在我的目标文件中&#xff0c;这个errmsg一共有112个副本&#xff0c;执行文件有8M左右。当我把errmsg放到C文件中&#xff0c;并为一千多个C文件加上了extern的声明后&#xff0c;所有的函数库文件尺寸都下降了20%左右&#xff0c;而我的执行文件只有5M了。一下子少了3M啊。

备注

有朋友对我说&#xff0c;这个只是一个特例&#xff0c;因为&#xff0c;如果errmsg在执行文件中存在多个副本时&#xff0c;可以加快程序运行速度&#xff0c;理由是errmsg的多个复本会让系统的内存换页降低&#xff0c;达到效率提升。像我们这里所说的errmsg只有一份&#xff0c;当某函数要用errmsg时&#xff0c;如果内存隔得比较远&#xff0c;会产生换页&#xff0c;反而效率不高。

这个说法不无道理&#xff0c;但是一般而言&#xff0c;对于一个比较大的系统&#xff0c;errmsg是比较大的&#xff0c;所以产生副本导致执行文件尺寸变大&#xff0c;不仅增加了系统装载时间&#xff0c;也会让一个程序在内存中占更多的页面。而对于errmsg这样数据&#xff0c;一般来说&#xff0c;在系统运行时不会经常用到&#xff0c;所以还是产生的内存换页也就不算频繁。权衡之下&#xff0c;还是只有一份errmsg的效率高。即便是像logmsg这样频繁使用的的数据&#xff0c;操作系统的内存调度算法会让这样的频繁使用的页面常驻于内存&#xff0c;所以也就不会出现内存换页问题了。



推荐阅读
  • 如何精通编程语言:全面指南与实用技巧
    如何精通编程语言:全面指南与实用技巧 ... [详细]
  • 在《Cocos2d-x学习笔记:基础概念解析与内存管理机制深入探讨》中,详细介绍了Cocos2d-x的基础概念,并深入分析了其内存管理机制。特别是针对Boost库引入的智能指针管理方法进行了详细的讲解,例如在处理鱼的运动过程中,可以通过编写自定义函数来动态计算角度变化,利用CallFunc回调机制实现高效的游戏逻辑控制。此外,文章还探讨了如何通过智能指针优化资源管理和避免内存泄漏,为开发者提供了实用的编程技巧和最佳实践。 ... [详细]
  • 你的问题在于:1. 代码格式混乱,缺乏必要的缩进,导致可读性极低;2. 使用 `strlen()` 和 `malloc()` 函数时,必须包含相应的头文件;3. `write()` 函数的返回值处理不当,建议检查并处理其返回值以确保程序的健壮性。此外,建议在编写代码时遵循良好的编程规范,增加代码的可维护性和可读性。 ... [详细]
  • 基于Net Core 3.0与Web API的前后端分离开发:Vue.js在前端的应用
    本文介绍了如何使用Net Core 3.0和Web API进行前后端分离开发,并重点探讨了Vue.js在前端的应用。后端采用MySQL数据库和EF Core框架进行数据操作,开发环境为Windows 10和Visual Studio 2019,MySQL服务器版本为8.0.16。文章详细描述了API项目的创建过程、启动步骤以及必要的插件安装,为开发者提供了一套完整的开发指南。 ... [详细]
  • 本文深入解析了WCF Binding模型中的绑定元素,详细介绍了信道、信道管理器、信道监听器和信道工厂的概念与作用。从对象创建的角度来看,信道管理器负责信道的生成。具体而言,客户端的信道通过信道工厂进行实例化,而服务端则通过信道监听器来接收请求。文章还探讨了这些组件之间的交互机制及其在WCF通信中的重要性。 ... [详细]
  • 优化后的标题:深入探讨网关安全:将微服务升级为OAuth2资源服务器的最佳实践
    本文深入探讨了如何将微服务升级为OAuth2资源服务器,以订单服务为例,详细介绍了在POM文件中添加 `spring-cloud-starter-oauth2` 依赖,并配置Spring Security以实现对微服务的保护。通过这一过程,不仅增强了系统的安全性,还提高了资源访问的可控性和灵活性。文章还讨论了最佳实践,包括如何配置OAuth2客户端和资源服务器,以及如何处理常见的安全问题和错误。 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • C语言中如何使用break语句强制跳出for循环及函数 ... [详细]
  • 在 Linux 环境下,多线程编程是实现高效并发处理的重要技术。本文通过具体的实战案例,详细分析了多线程编程的关键技术和常见问题。文章首先介绍了多线程的基本概念和创建方法,然后通过实例代码展示了如何使用 pthreads 库进行线程同步和通信。此外,还探讨了多线程程序中的性能优化技巧和调试方法,为开发者提供了宝贵的实践经验。 ... [详细]
  • 线程池是一种预先创建并维护多个线程的机制,以便在需要时能够快速响应任务请求,避免了频繁创建和销毁线程的开销。本文将详细介绍如何使用C语言在Linux环境中构建一个高效且动态的线程池,以提高系统性能和资源利用率。通过合理的设计和优化,该线程池能够在高并发场景下表现出色,适用于各种服务器端应用。 ... [详细]
  • 数字图书馆近期展出了一批精选的Linux经典著作,这些书籍虽然部分较为陈旧,但依然具有重要的参考价值。如需转载相关内容,请务必注明来源:小文论坛(http://www.xiaowenbbs.com)。 ... [详细]
  • C++ 开发实战:实用技巧与经验分享
    C++ 开发实战:实用技巧与经验分享 ... [详细]
  • 如何利用Java 5 Executor框架高效构建和管理线程池
    Java 5 引入了 Executor 框架,为开发人员提供了一种高效管理和构建线程池的方法。该框架通过将任务提交与任务执行分离,简化了多线程编程的复杂性。利用 Executor 框架,开发人员可以更灵活地控制线程的创建、分配和管理,从而提高服务器端应用的性能和响应能力。此外,该框架还提供了多种线程池实现,如固定线程池、缓存线程池和单线程池,以适应不同的应用场景和需求。 ... [详细]
  • 具备括号和分数功能的高级四则运算计算器
    本研究基于C语言开发了一款支持括号和分数运算的高级四则运算计算器。该计算器通过模拟手算过程,对每个运算符进行优先级标记,并按优先级从高到低依次执行计算。其中,加减运算的优先级最低,为0。此外,该计算器还支持复杂的分数运算,能够处理包含括号的表达式,提高了计算的准确性和灵活性。 ... [详细]
  • 在PHP中使用`exec`函数执行Shell命令时,需要特别注意安全性问题。当使用来自远程的数据构建命令时,极易引发命令注入漏洞。本文详细探讨了如何避免使用污染数据,确保系统命令的安全执行,并提供了多种防护措施和最佳实践。 ... [详细]
author-avatar
Hydhuyfh________
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有