Linux下C语言编程中的一些经验小结

本文最后更新于2017年8月19日,已超过 1 年没有更新,如果文章内容失效,还请反馈给我,谢谢!

=Start=

缘由:

总结、提高需要

正文:

参考解答:

1、在使用一个返回类型为指针的函数时,一定要明确知道是否需要在使用后free,因为不free会造成内存泄露,但不需要free却又主动free的话会造成程序异常且不容易检测;比如get_current_dir_name()函数返回的指针就需要主动free(),在它的手册中提到它是malloc()后返回的,需要调用者对非NULL返回调用free(),但是ttyname()返回的指针就不应该手动free(),因为ttyname()返回的是一个静态分配的字符串;

2、尽量通过传参的方式获取处理结果,避免通过函数内部malloc后返回指针的方式,一来是避免调用者忘记free导致的内存泄露,或者主动free导致的程序异常,二来是这样编写的函数是可重入且线程安全的;比如 ttyname() 和 ttyname_r() 中就应该优先使用ttyname_r();

3、关键路径上使用的函数最好要是异步的,避免成为瓶颈从而引发故障;比如:syslog()函数其实是同步而非异步的,具体信息可参见「rsyslog使用之坑」;

4、不要相信传入函数的参数,函数中的第一步操作就应该是参数的合理/合法性校验,不通过的话直接return,既高效又可避免错误;比如下面这个函数:

存在几个问题:1.现有的两个参数应该显式改为三个参数(因为C语言中的函数参数是没有数组这一类型的,它对于函数来说就是一指针,指针指向的原始数组大小需要用另一个参数来显式说明);2.函数体中第一步应该做参数的合理性校验,避免后面出现对NULL指针进行操作导致segmentation fault,同时也可以提高效率;3.对于系统调用read()的返回值没有做判断以及相应的操作,这样虽然对cmdline的内容没什么影响,但是函数的返回值却会是0,可能会造成调用者的误解并引发后续错误。修改后的效果如下:

增加了一些检查、判断之后,整体逻辑要清晰了不少,也可避免不少不必要的错误。

补充一个memset()对NULL指针进行操作导致「段错误」的示例:

 

参考链接:

=END=

声明: 除非注明,ixyzero.com文章均为原创,转载请以链接形式标明本文地址,谢谢!
https://ixyzero.com/blog/archives/3561.html

《Linux下C语言编程中的一些经验小结》上有7条评论

  1. Linux下如何为C语言项目添加合适的log机制

    深入理解log机制
    http://feihu.me/blog/2014/insight-into-log/

    How to implement a leveled debug system?
    https://stackoverflow.com/questions/2181451/how-to-implement-a-leveled-debug-system

    如何在日志文件中引入日期和时间信息(How to introduce date and time in log file) #简单易用(不过只是简单的printf,还没有写文件的功能)
    https://stackoverflow.com/questions/7411301/how-to-introduce-date-and-time-in-log-file

    [C++]How to implement a good debug/logging feature in a project
    https://stackoverflow.com/questions/6168107/how-to-implement-a-good-debug-logging-feature-in-a-project
    http://logging.apache.org/log4cxx/latest_stable/index.html

    Super fast C++ logging library.
    https://github.com/gabime/spdlog

    https://codingfreak.blogspot.com/2010/08/printing-logs-based-on-log-levels-in-c.html

  2. 深入理解可重入与线程安全
    https://blog.csdn.net/feiyinzilgd/article/details/5811157

    如果一个函数能够安全的同时被多个线程调用而得到正确的结果,那么,我们说这个函数是线程安全的。所谓安全,一切可能导致结果不正确的因素都是不安全的调用。

    线程安全,是针对多线程而言的。那么和可重入联系起来,我们可以断定,可重入函数必定是线程安全的,但是线程安全的,不一定是可重入的。不可重入函数,函数调用结果不具有可再现性,可以通过互斥锁等机制,使之能安全的同时被多个线程调用,那么,这个不可重入函数就是转换成了线程安全。

    线程安全,描述的是函数能同时被多个线程安全的调用,并不要求调用函数的结果具有可再现性。也就是说,多个线程同时调用该函数,允许出现互相影响的情况,这种情况的出现需要某些机制比如互斥锁来支持,使之安全。

    ==

    可重入函数,描述的是函数被多次调用但是结果具有可再现性。

    为了保证函数是可重入的,需要做到一下几点:
    1,不在函数内部使用静态或者全局数据
    2,不返回静态或者全局数据,所有的数据都由函数调用者提供
    3,使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据
    4, 如果必须访问全局数据,使用互斥锁来保护
    5,不调用不可重入函数

  3. 如何写好 C main 函数
    https://zhuanlan.zhihu.com/p/68427001

    main 函数是唯一的。
    main() 函数是开始执行时所执行的程序的第一个函数,但不是第一个执行的函数。第一个函数是 _start(),它通常由 C 运行库提供,在编译程序时自动链入。此细节高度依赖于操作系统和编译器工具链,所以我假装没有提到它。

    main() 函数有两个参数,通常称为 argc 和 argv,并返回一个有符号整数。大多数 Unix 环境都希望程序在成功时返回 0(零),失败时返回 -1(负一)。

    参数名称描述argc参数个数参数向量的个数argv参数向量字符指针数组

    参数向量 argv 是调用你的程序的命令行的标记化表示形式。

    当我从头开始编写 main.c 时,它的结构通常如下:

    /* main.c */
    /* 0 版权/许可证 */
    /* 1 包含 */
    /* 2 定义 */
    /* 3 外部声明 */
    /* 4 类型定义 */
    /* 5 全局变量声明 */
    /* 6 函数原型 */

    int main(int argc, char *argv[]) {
    /* 7 命令行解析 */
    }

    /* 8 函数声明 */

发表评论

电子邮件地址不会被公开。 必填项已用*标注