unix 编程随笔

来源:互联网 发布:c 构造函数编程例子 编辑:程序博客网 时间:2024/06/07 16:32
该篇为本人在工作编程中的一些心得体会,愿初入此道的小生们少走些弯路,我只愿面朝大海,春暖花开。(持续更新)

参考书籍

《C程序设计》谭浩强 编著
《C++面向对象程序设计》谭浩强 编著
《UNIX环境高级编程》等

  • 重要性
    对某个知识点(或函数或类)的概念的理解,你可能会百度,goole等查一下,这只能得到个大概(或众说纷纭或没有),并且是别人的观点,不一定准确。若要深入正确的理解,你需要权威,也就是参考书。

  • 怀疑的态度
    但本人大多数时候喜欢百度,因为我只需要一个大概的理解,网上搜一下快捷方便。此时你需要理智的接受信息,而不是一股脑全信。一般一个新的信息你不懂,需暂时相信,或在多个搜索中筛选判断,形成自己的判断,这个判断也请在内心保留一份怀疑,它只适用现在的狭隘环境。
    我大学时常常觉得我对c,c++的知识点很是理解,理论考试及实验编程基本满分(给我减了一分可能是怕我傲娇),觉得自己蛮拽的,接着打脸的time到了。工作中编程总出现这啊那啊的问题,其中就很有些概念性的问题,比如指针之类,老是惊叹一声“阿西吧,居然可以这么用”“我去,是这么回事啊,我真是天才”,我发现,随着时间及我处理的问题的变化,对概念的理解是在慢慢改变或有新的理解。以前的理解总有这啊那啊的错误。然后我开始怀疑人生。
    是的,没有绝对的真理,它可能适应某一种环境,但在某一其它特定环境,它又是一种错误。就拿力学来说,19世纪是牛顿的天下,经典力学牛掰轰轰,但现代物理学就易主了,薛定谔的量子力学和爱因斯坦的相对论。所以说没有绝对的真理,说不定22世纪的物理学理论奠基人就是我了。(不要拿鸡蛋砸我,你就那么肯定不是我,都说了没有绝对,你这孩子。就算不是我,也可能是zhaozhongtao吧)

  • 权威
    谭浩强的这两本教科书,权威中的权威,我每每翻开查找,都会有新的发现,觉得在某些方面蛮强大的,因为以前觉得它只是为初学者设计的,轻视它。但它的定位是对概念的精准阐述,并没有深入讲解。因此你需要选择一本专业方向的书籍。
    我选的这本《UNIX环境高级编程》就是对各种函数的精准解析,适用范围,以及C99对C89的改进及原因分析。本人受益良多。

缓冲区溢出

  • 概念
    缓冲区溢出,简单的说就是计算机对接收的输入数据没有进行有效的检测 (理想的情况是程序检查数据长度并不允许输入超过缓冲区长度的字符),向缓冲区内填充数据时超过了缓冲区本身的容量,而导致数据溢出到被分配空间之外的内存空间,使得溢出的数据覆盖了其他内存空间的数据。

  • 原理图
    缓冲区溢出微观图这里写图片描述

  • 危害
    两种危害:
    在当前网络与分布式系统安全中,被广泛利用的50%以上都是缓冲区溢出,其中最著名的例子是1988年利用fingerd漏洞的蠕虫。而缓冲区溢出中,最为危险的是堆栈溢出,因为入侵者可以利用堆栈溢出,在函数返回时改变返回程序的地址,让其跳转到任意地址,带来的危害一种是程序崩溃导致拒绝服务,另外一种就是跳转并且执行一段恶意代码,比如得到shell,然后为所欲为。

    概念及危害来自百度百科缓冲区溢出

  • 格式化输出函数sprintf 与snprintf, 字符串拷贝函数strcpy与strncpy
    Prototype: int sprintf (char *buf, const char *template, …)
    Prototype: int snprintf (char *buf, size_t n, const char *template, …)
    为了解决sprintf函数产生的缓冲区溢出问题,引入snprintf函数。
    snprintf函数的缓冲区长度是一个显式参数,明确指定向buf中写入最多n个字节。

与此类似的标准函数strcpy()、strcat()、vsprintf()、gets()、scanf()等,请用带显示参数的改进版的较安全函数。
Note:使用这些改进的较安全的函数的条件是用C99标准(或C11)的编译器。

  • snprintf函数并非绝对安全。
    snprintf函数仍然可能会产生缓冲区溢出。
    我一开始以为该函数会彻底解决该漏洞(因为有人把它们误称为安全函数,其实它们是相对于C89标准的不安全的sprintf函数之类而言的,我把它称之为较安全函数),其实不然。我做过一个测试,当将参数n的值赋予一个比buf长度大的值后,同样会产生缓冲区溢出。因此必须人为确保该参数n的大小,一般好的习惯是让n=strlen(buf)。调用者有责任确保向buf中写入的字节数在buf的长度范围内。

至于C的新标准为什么不将该漏洞在底层彻底封死,在下仅作以下推测:
1,C是自由风格的编程语言,在底层封死该漏洞会与之冲突。说人话就是,这样做会将其它的大量的且重要的与之相关的函数的功能给封掉或者重构。
2,每次检测边界太耗时了,该猜测来自C++的容器类vector,他的成员函数at()的实现中就会检测指针是否越界,但它的重载运算符“[]”就没有该检测机制,依然会溢出。而没有禁掉“[]”的原因是at()函数每次检测太耗时了。

//测试源代码test.c#include<stdio.h>#include<stdlib.h>int main(){    char *buf;    buf = (char *)malloc(10);    if(0 == buf)    {        printf("fail to malloc!\n");    }    printf("给buf分配10个字节缓冲区\n");    int n = 0;    printf("将12个字节的字符串写入buf指向的缓冲区\n");    n = snprintf(buf, 15, "123456789abc");    printf("n = %d,\nbuf = %s\n", n, buf);    printf("buf[9] = %c\n", buf[9]);    printf("buf[10] = %c\n", buf[10]);    printf("buf[11] = %c\n", buf[11]);    printf("buf[12] = %c\n", buf[12]);    printf("buf[13] = %c\n", buf[13]);    free(buf);    return 0;}

测试结果

xiaobai@xiaobai-kylin:~/Documents/test$ gcc test.cxiaobai@xiaobai-kylin:~/Documents/test$ ./a.out 给buf分配10个字节缓冲区将12个字节的字符串写入buf指向的缓冲区n = 12,buf = 123456789abcbuf[9] = abuf[10] = bbuf[11] = cbuf[12] = buf[13] = xiaobai@xiaobai-kylin:~/Documents/test$ 

异常处理

渐渐发现,有时处理一个bug花数个星期的原因是代码中没有良好的异常处理,从而在日志中很难发现异常,导致不得不花大量时间模拟并复现问题,费时费力。验证了一句真理:“好吃不过饺子,好代码不如异常处理”。

内存泄漏

C 与C++

C++是C的超集。针对C的不足做的改进,所以叫C plus plus。
C面向过程,C++侧重于面向对象。
C++对C的“增强”,表现在两个方面:
(1)在原来面向过程的机制基础上,对C语言的功能作了不少扩充。
(2)增加了面向对象的机制。

  • 用const定义常变量
  • 变量的引用
    主要用途:将引用作为函数参数,解决用指针传参的“兜圈子”问题,简单易用。
  • try-catch结构的异常处理
  • 封装
    修改程序是方便,只需修改类中的一处,其它地方的对象的处理都无需修改。

代码(编写)修改原则

  • 兼容旧版本
    针对大型的多规模的服务器,兼容旧版本非常重要。

    • 配置文件
      避免手动修改大量配置数据,及误操作导致的问题。
    • 存档文件
      比如,直播(或点播)服务器中的存档文件中存储了大量的节目源及一系列配置(这些配置来自其他模块的手动下发)。当程序维护或异常重启后,会读取存档文件继续先前的工作,而无需重新手动下发到该服务器。但若新版本修改了该存档文件的格式等等,那么就需要删除旧数据,并重新下发,而对于成千上万的节目服务器来说,会是很大的工作量,给运维人员带来很大的负担。
  • 最优先打开日志文件系统
    在写这条的前一分钟(2016-12-26),我刚将两个星期前添加新功能的代码的错误找到。原因是新增的功能代码放在了初始化日志系统的前面,并且该代码必须是daemon守护进程模式启动,在控制台无法看到调试信息,而日志文件中没有新增的调试信息,使我误以为daemon后的信号处理出错,并往这方向找了好久都没结果。
    注意:若有些系统的daemon()实现会关闭所有打开的文件描述符,则初始化日志系统应放在daemon()后面。

我并非是花了两个星期找到该bug,而是其它重要任务使我将该bug搁置了这么久。当我再次处理该bug时,花了不到半个小时。我想说的是,当找bug找了很久都没找到的时候,你已陷入思(神)维(智)误(不)区(清),此时需要静一静,回头重新从大局出发,你就会看到庐山真面目。

  • 排它性,单一原则
    我做过的比较蠢的事情就是,修改代码时,中途发现其它的小瑕疵,忍不住修改之(有点强迫症,虽然我不是处女座的)。待将所有代码修改完后测试,而测试有问题并且日志无异常报告,凭感觉修改还是找不到。然后我就尴尬了,因为你无法确定多处修改中的哪段出现问题,就又得重新还原,一处一处检测,这就是所谓的“no zuo no die”吧。
    以上只是比较蠢的,接下来是更蠢的,也是最坑的。反馈人提出了问题,然后我就埋头死命的找,也不与反馈人沟通,死活找不到(这里插一句,一般我很难快速找到问题根源的重要原因之一是,日志文件中无异常打印,这让我越来越对异常处理相当的重视。我对自己能力的提升理解不再是解决了问题,而是知道如何解决问题)。后来发现问题在其它部分,运行的程序版本,运行的环境,配置文件,出问题时的其它操作,媒体源的卡顿,中断等等等等。一开始时会抱怨别人,说提供问题时这啊那啊都不说清楚,坑爹之类的话,后来渐渐发觉,不是别人坑你,而是你自己坑了自己,当你拿到问题时就应该将各方面都考虑到,主动询问别人该问题的配置环境等,这才是处理问题正确的打开方式。

说到单一性,想到以前比较搞笑的事。
STB部门向我们平台反馈了个bug,说我们提供的点播节目有问题。结果我最后发现是他们播放器的问题。但神奇的是,我居然真在我们这边的代码中找到bug。(从此以后,我给自己起了个代号—-The king of bug。)
因为这个问题是客户端与服务端程序交互,不应该断定是某一方的问题,应该抱有怀疑的态度,然后一边一边的排除。
我这边从日志方面看不出问题,我就仔细梳理代码,还真发现代码对ts流数据帧的处理有出入,将其修正。然而该问题还是存在,现在对修改后的自己的代码比较自信了,就猜测是不是他们那边没处理好。果真,测试后发现是他们播放器快进的问题。

  • 容错能力
    作为服务器端的程序的稳定性一定要好,不能因为外界的环境动不动就挂掉,而这就得靠代码较好的容错能力。比如,程序有个功能是接收一个“Hi”的字符串,然后处理。但意外收到个“Hello”的字符串,若程序没有对其相关的处理,可能就会意外退出。所以程序应该加一个对其他信息的处理,比如忽略。这就是容错能力。

守护进程

守护进程没有控制终端,所以说它们是在后台运行的。
但守护进程不等价与后台进程,比如shell中运行程序时在后面添加“&”会使其变成后台进程,但它依然受控制终端的信号影响。
linux中使用daemon()函数可使程序变为守护进程,如下:
这里写图片描述
此时孤儿进程没有控制终端(TTY为?),但PID=PGID=SID,该进程仍有机会被分配到一个控制终端。若在daemon()后面再fork一次,则能彻底解决该问题。如下:
这里写图片描述
此时,守护进程在孤儿进程组中,并且不是会话首进程,非常安全。

1 0
原创粉丝点击