存档:2008年十月

server client

十月 31, 2008 | linux | RSS 2.0

渐渐喜欢上了linux下的c,不过学习的有点急于求成了。
client
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define PORT 3490
#define MAXDATESIZE 5000
int main(int argc, char ** argv)
{
    int sockfd,nbytes;
    char buf[1024];
    struct hostent *he;
    struct sockaddr_in srvaddr;
    if(argc!=2) {
        perror(”Usage:client host name”);
        exit(1);
    }
    if((he=gethostbyname(argv[1]))==NULL){
        perror(”get host by name is wrong”);
        exit(1);
    }
    if((sockfd=socket(AF_INET,SOCK_STREAM,0)) == -1) {
   
        perror(”create socket is wrong “);
        exit(1);
    }
    bzero(&srvaddr,sizeof(srvaddr));
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(PORT);
    srvaddr.sin_addr=*((struct in_addr *)he->h_addr);
    if(connect(sockfd,(struct sockaddr *)&srvaddr,sizeof(struct sockaddr)) ==-1) {
        perror(”connetc is wrong”);
        exit(1);
   
    }
    if((nbytes == read(sockfd,buf,MAXDATESIZE)) == -1) {
        perror(”read error”);
        exit(1);
    }
    buf[nbytes] =”0″;
    printf(”read : %s”,buf);
    close(sockfd);

}

#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#define MYPORT 3490
#define BACKLOG 5
main() {
    int sockfd ,new_fd;
    struct sockaddr_in srvaddr;
    struct sockaddr_in cliaddr;
    int sin_size;
    if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) {
        perror(”socket error”);
        exit(1);
    }
    bzero(&srvaddr,sizeof(srvaddr));
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(MYPORT);
    if(bind(sockfd,(struct sockaddr *)&srvaddr,sizeof(struct sockaddr)) == -1) {
        perror(”bind error”);
        exit(1);
    }
    if(listen(sockfd,BACKLOG)==-1) {
        perror(”listen error”);
        exit(1);
    }
    for(;;) {
        sin_size = sizeof(struct sockaddr_in);
        if((new_fd=accept(sockfd,(struct sockaddr*)&cliaddr,&sin_size)) == -1) {
            perror(”accept error”);
            exit(1);
        }
        printf(”server : got connection from %s “,inet_ntoa(cliaddr.sin_addr));
        if(write(new_fd,”hello network”,14)==-1)
          perror(”write error”);
        close(new_fd);

    }
    close(sockfd);

}

没有评论 »

vi高级应用

十月 29, 2008 | linux, tools | RSS 2.0

find ./ -name “*.doc” -exec rm {} ;

2,用find , grep, xargs 的组合

 find | grep .doc$ | xargs -i rm {}

  看上去,第一个简单一些,但很类似,不过如果要实现:
  删除那些没有指定后缀的文件,那用find的时候,文件名的匹配,似乎要麻烦一些,而用后者就来得很轻松。

find | grep -v .doc$ | xargs -i rm {}

1.交换两个字符位置
xp
其实就是删除光标当前字符(x),然后再将缓存的字符贴出(p)

2.上下两行调换
ddp
实际就是(dd)删除当前行,(p)后即将缓存的行贴出

3.把文件内容反转
:g/^/m0
找到每一行,然后m移动到当前第一行的上面

4.上下两行合并
J
实用性:高,shift+j

5.删除所有行
dG
(d)为删除,(shift+g)到文件尾,因此这命令效用为从光标处删除至文件尾
那么删除到文首呢?对了,如果知道gg可以将光标移至文首,那么dgg便是删除到文首了
类似的还有:
dw — 删到词尾
db — 删到词头
daw — 删除光标所在词(较有用)

如果你用 "c" 代替 "d",这会变成修改命令;而改用 "y",则变成拷贝命令,等等等等。

6.从当前位置删除到行尾
d$
(d)删除,$ (即 shift+4)为行尾,^( 即 shift+6)为行头,依此类推,d^删除至行头
更为简洁的命令为D,对了,就是shift+d,就这么简单

7.从当前位置复制到行尾
y$ 如果要粘贴到其他地方 p 就可以了
同上

由于vi 是建立在 EX 上的 所以 当键入 : 时就来到了 EX 命令状态

8.
:ab string strings
例如 “:ab usa United States of America” ,
当你在文件里插入 usa 时
United States of America 就蹦出来了
其实执行的是":abbreviate"命令,用":unabbreviate"命令解除缩写,要删除全部缩写用":abclear"

9.
:map keys new_seq
定义你当前 键盘命令
键映射,我前面的tips有用到

10.
:set [all]
vi or ex 的编辑状态
如 显示每行 :set nu
set,就是set,不显示行号”:se nonu”

11.
在命令状态下,nyy表示拷贝从光标行起的下n行内容,p表示paste,可刚复制的内容粘贴在光标处的
下面。
讲的太不清楚了,n用数字代替,即将后面的指令重复执行n次,yy为复制,nyy复制n行

12.
单个字符替换用r,覆盖多个字符用R,用多个字符替换一个字符用s,整行替换用S
实用性:高,S等于我常用的cc或C

13.
:%s/old_word/new_word/g
这个指令是于在整个文件中替换特定字符串
替换,用法是这样:”:起始行,结束行s/搜索串/替换串/gc” 从起始行到结束行,把所有的搜索串替换为替换串,最后那个”c”为开启交互式替换,%为全文

14.光标控制
k:上移 nk 上移n行
j:下移 nj 下移n行
光标移动,还有h:左移  l:右移,不过我更喜欢用上、下、左、右,嘿嘿

将光标移到第n行,按下 mk
将光标移到第m行,按下 “ay’k
即将第n到m的行存到a寄存器,以此类推,b,c……..寄存器等
这样就可以将你常用的需要复用的内容粘贴到不同的寄存器中以备用
想粘贴到某处,直接将光标移到某地,按下 ‘ap 即可,以此类推,b,c……..寄存器等

在当前屏幕中
H 跳到第一行
M 跳到中间一行
L 跳到最后一行
当前屏幕的光标控制技巧,助记方法为:Head、Middle、Last

15.
表8-2 删除命令
删除命令操作
d l 删除当前字符(与x命令功能相同)
d 0 删除到某一行的开始位置
d ^ 删除到某一行的第一个字符位置(不包括空格或TA B字符)
d w 删除到某个单词的结尾位置
d 3 w 删除到第三个单词的结尾位置
d b 删除到某个单词的开始位置
d W 删除到某个以空格作为分隔符的单词的结尾位置
d B 删除到某个以空格作为分隔符的单词的开始位置
d 7 B 删除到前面7个以空格作为分隔符的单词的开始位置
d) 删除到某个语句的结尾位置
d 4) 删除到第四个语句的结尾位置
d( 删除到某个语句的开始位置
d } 删除到某个段落的结尾位置
d { 删除到某个段落的开始位置
d 7 { 删除到当前段落起始位置之前的第7个段落位置
d d 删除当前行
d /t e x t 删除从文本中出现” t e x t”中所指定字样的位置,一直向前直到下一个该字样所出现的
位置(但不包括该字样)之间的内容
d fc 删除从文本中出现字符”c”的位置,一直向前直到下一个该字符所出现的位置(包括
该字符)之间的内容
d tc 删除当前行直到下一个字符” c”所出现位置之间的内容
D 删除到某一行的结尾
d $ 删除到某一行的结尾
5 d d 删除从当前行所开始的5行内容
d L 删除直到屏幕上最后一行的内容
d H 删除直到屏幕上第一行的内容
d G 删除直到工作缓存区结尾的内容
d 1 G 删除直到工作缓存区开始的内容

修改命令操作
c l 更改当前字符
c w 修改到某个单词的结尾位置
c 3 w 修改到第三个单词的结尾位置
c b 修改到某个单词的开始位置
c W 修改到某个以空格作为分隔符的单词的结尾位置
c B 修改到某个以空格作为分隔符的单词的开始位置
c 7 B 修改到前面7个以空格作为分隔符的单词的开始位置
c 0 修改到某行的结尾位置
c) 修改到某个语句的结尾位置
c 4) 修改到第四个语句的结尾位置
c( 修改到某个语句的开始位置
c } 修改到某个段落的结尾位置
c { 修改到某个段落的开始位置
c 7 { 修改到当前段落起始位置之前的第7个段落位置
c tc 修改当前行直到下一个字符c所出现位置之间的内容
C 修改到某一行的结尾
c c 修改当前行
5 c c 修改从当前行所开始的5行内容

.重复上一次修改!

表8-4 替换命令
替换命令操作
s 将当前字符替换为一个或多个字符
S 将当前行替换为一个或多个字符
5 s 将从当前字符开始的5个字符替换为一个或多个字符

vi替换使用规则:
:g/s1/s/s2/s3/g
第一个g表示对每一个包括s1的行都进行替换,第二个g表示对每一行包括s1的行所有的s2都用s3替换
s表示替换,s2是要被替换的字符串,他可以和s1相同(如果相同的话用//代替),s3是替换字符串

16.
fx
往右移动到 x 字符上
Fx
往左移动到 x 字符上
tx
往右移动到 x 字符前
Tx
往左移动到 x 字符后
(注意:以上四个命令中,其中x是键入的字符)
;
分号,配合 f 和 t 使用,重复一次
,
逗号,配合 f 和 t 使用,反方向重复一次
如果不结合;与,很不好用,nfx为移到第n个x字符上

17. vi 环境选项 Solaris ksh

noautoindent nomodelines noshowmode
autoprint nonumber noslowopen
noautowrite nonovice tabstop=8
nobeautify nooptimize taglength=0
directory=/var/tmp paragraphs=IPLPPPQPP LIpplpipnpbtags=tags /usr/lib/tags
noedcompatible prompt tagstack
noerrorbells noreadonly term=vt100
noexrc redraw noterse
flash remap timeout
hardtabs=8 report=5 ttytype=vt100
noignorecase scroll=11 warn
nolisp sections=NHSHH HUuhsh+c window=23
nolist shell=/bin/ksh wrapscan
magic shiftwidth=8 wrapmargin=0
mesg noshowmatch nowriteany

For C-Shell:
setenv EXINIT “set nu”
For Bourne or Korn Shell:
EXINIT=”set nu”; export EXINIT
For Korn Shell Only (alternate method):
typeset -x EXINIT=”set nu”
在 .profile 里设置 vi 的环境选项 , 以上均测试过
以上的查阅vim帮助文档,太多了不一一解释了

18.标记文本

mchar   用字母char标记当前光标的位置
`char   移至char所标记处
‘char   移至char标记所在行的开头处
“     移至当前行上一次所在位置(在光标移动之后)――一个双引号
”    移至当前行上第一次所在位置的行的开头处(在光标移动之后)――两个单引号0.
头两个和最后一个就已经挺好用的了

19.
同时vi多个文件时,CTRL-SHIFT-6回到上一个文件,在本次vi的文件和上次vi的文件之间切换。
但是我发现一个BUG:在用CTRL-SHIFT-6切换到上一个文件后,用:args查看多文件vi状态时,
屏幕底部仍然显示目前vi的是刚才的文件。
(在HP-UX,Solaris,AIX上通过)
也可以使用:
:e#
进行切换
ctrl+shift+6=:e#回到上一个文件,哇哈哈,终于找到了,对应的是:next简写为:n到下个文件,用files貌似更能看清当前打开的文件列表

20.
sco 下VI 要在文本前同样的字符加用
%s/^/要加的内容/g 要在文本后同样的字符加
%s/$/要加的内容/g
汗,这样利用查找替换

21.
如何去掉文本中的 ^M 硬回车?不必用binary传回去再ascii传回来的方式,用shell或者unix语句实现。

cat filename |tr -d ‘015′ >newfile
不同的unix系统还存在一些其他不同的命令,如:doscp
sed 也可以实现这个功能.

dos2unix filename filename2
反之
unix2dos filename filename2

在vi 中用:$s/^M//g
^是crtl-V crtl-M
我常用的是dos2unix

22.如何在”unix命令行”下将一个文件的某字符串用另一个串换掉

sed ’s/string1/string2/gp’ file1 > file2

23.将/etc/hosts下所有的地址都ping 2次

1 #/usr/bin/sh
2 #grad /etc/hosts and ping each address
3 cat /etc/hosts|grep -v ‘^#’ | while read LINE
4 do
5 ADDR=`awk ‘{print $1}’`
6 for MACHINE in $ADDR
7 do
8 ping $MACHINE -n 2
9 done
10 done

24.
到前一个函数[[ ,到下一个函数]] ,括号配对% ,交叉参考Ctrl_] (事先用ctags做索引),回来用e# ` 编辑一个函数:vi -t 函数名 ,编辑加密文本vi -X
%括号匹配我介绍过,:e#回来可以用ctrl+o替代,更优雅

25.
在插入模式下ctrl+p,自动补齐剩余单词,以赖规则:tags,以有的单词等等
还ctrl_n呢

 

  • 在编辑模式下,可通过gqap命令对注释自动断行(每行字符个数可通过命令模式下的”set textwidth=个数”设定)
  • 命令模式下输入数字可以直接跳到指定行,也可在打开文件时用“vim +数字 文件名”实现相同的功能。
  • 命令模式下的TOhtml命令可把C语言输出为html文件,结合syntax on,可产生比较好的web page把代码发布出去。
  • 先切换到可视模式(编辑模式下按字母v可切换过来),用光标选中一片代码,然后通过命令模式下的命令“s#^#//#g”把某一片代码给注释掉,这非常方便调试某一片代码的功能。
  • 命令模式下的”set paste“可解决复制本来已有缩进的代码的自动缩进问题,后可执行”set nopaste“恢复自动缩进。
  • 为了使用最新的vim特性,可用”set nocp”取消与老版本的vi的兼容。
  • 如发现变量命名不好,想在整个代码中修改,可在命令模式下用”%s#old_variable#new_variable#g”全局替换。替换的时注意变量名是其他变量一部分的情况。
  • 如果想把缩进和TAB键替换成空格,可考虑设置expandtab,即“set et”,如果要把以前编写的代码中的缩进和TAB键都替换掉,可以用retab。
  • 为实现关键字补全,输入一部分字符后,按下CTRL+P即可。比如先输入prin,然后按下CTRL+P就可以补全了。
  • 如果想在在编辑模式下查看Linux手册,可把光标定位到在某个函数,按下Shift+k就可以调出man,很有用。
  • 删除空行,在命令模式下输入g/^$/d,前面g命令是扩展到全局,中间是匹配空行,后面d命令是执行删除动作。用替换也可以实现,键入%s#^##g,意思是把所有以换行开头的行全部替换为空。类似地,如果要把多个空行转换为一个可以输入g/^$/d或者%s#^$##g。
  • 注意使用一些有用的插件,比如ctags, cscope等,可以提高代码阅读、分析的效率。特别是open source的东西。


  • 没有评论 »

    c编程修养

    十月 26, 2008 | c/c++, 软件工程/编程技巧/设计模式 | RSS 2.0

      什么是好的程序员?是不是懂得很多技术细节?还是懂底层编程?还是编程速度比较快?

      我觉得都不是。对于一些技术细节来说和底层的技术,只要看帮助,查资料就能找到,对

      于速度快,只要编得多也就熟能生巧了。

      我认为好的程序员应该有以下几方面的素质:

      1、有专研精神,勤学善问、举一反三。

      2、积极向上的态度,有创造性思维。

      3、与人积极交流沟通的能力,有团队精神。

      4、谦虚谨慎,戒骄戒燥。

      5、写出的代码质量高。包括:代码的稳定、易读、规范、易维护、专业。

      这些都是程序员的修养,这里我想谈谈“编程修养”,也就是上述中的第5点。我觉得,如

      果我要了解一个作者,我会看他所写的小说,如果我要了解一个画家,我会看他所画的图

      画,如果我要了解一个工人,我会看他所做出来的产品,同样,如果我要了解一个程序员

      ,我想首先我最想看的就是他的程序代码,程序代码可以看出一个程序员的素质和修养,

      程序就像一个作品,有素质有修养的程序员的作品必然是一图精美的图画,一首美妙的歌

      曲,一本赏心悦目的小说。

      我看过许多程序,没有注释,没有缩进,胡乱命名的变量名,等等,等等,我把这种人统

      称为没有修养的程序,这种程序员,是在做创造性的工作吗?不,完全就是在搞破坏,他

      们与其说是在编程,还不如说是在对源程序进行“加密”,这种程序员,见一个就应该开

      除一个,因为他编的程序所创造的价值,远远小于需要在上面进行维护的价值。

      程序员应该有程序员的修养,那怕再累,再没时间,也要对自己的程序负责。我宁可要那

      种动作慢,技术一般,但有良好的写程序风格的程序员,也不要那种技术强、动作快的“

      搞破坏”的程序员。有句话叫“字如其人”,我想从程序上也能看出一个程序员的优劣。

      因为,程序是程序员的作品,作品的好坏直截关系到程序员的声誉和素质。而“修养”好

      的程序员一定能做出好的程序和软件。

      有个成语叫“独具匠心”,意思是做什么都要做得很专业,很用心,如果你要做一个“匠

      ”,也就是造诣高深的人,那么,从一件很简单的作品上就能看出你有没有“匠”的特性

      ,我觉得做一个程序员不难,但要做一个“程序匠”就不简单了。编程序很简单,但编出

      有质量的程序就难了。

      我在这里不讨论过深的技术,我只想在一些容易让人忽略的东西上说一说,虽然这些东西

      可能很细微,但如果你不注意这些细微之处的话,那么他将会极大的影响你的整个软件质

      量,以及整个软件程的实施,所谓“千里之堤,毁于蚁穴”。

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

      这就是程序员的——编程修养。我总结了在用C/C++语言(主要是C语言)进行程序写作上

      的三十二个“修养”,通过这些,你可以写出质量高的程序,同时也会让看你程序的人渍

      渍称道,那些看过你程序的人一定会说:“这个人的编程修养不错”。

      ————————————————————————

      01、版权和版本

      02、缩进、空格、换行、空行、对齐

      03、程序注释

      04、函数的[in][out]参数

      05、对系统调用的返回进行判断

      06、if 语句对出错的处理

      07、头文件中的#ifndef

      08、在堆上分配内存

      09、变量的初始化

      10、h和c文件的使用

      11、出错信息的处理

      12、常用函数和循环语句中的被计算量

      13、函数名和变量名的命名

      14、函数的传值和传指针

      15、修改别人程序的修养

      16、把相同或近乎相同的代码形成函数和宏

      17、表达式中的括号

      18、函数参数中的const

      19、函数的参数个数

      20、函数的返回类型,不要省略

      21、goto语句的使用

      22、宏的使用

      23、static的使用

      24、函数中的代码尺寸

      25、typedef的使用

      26、为常量声明宏

      27、不要为宏定义加分号

      28、||和&&的语句执行顺序

      29、尽量用for而不是while做循环

      30、请sizeof类型而不是变量

      31、不要忽略Warning

      32、书写Debug版和Release版的程序

      21、goto语究 使劲

      22、宏的使用

      23、static的使用

      24、函数中的代码尺寸

      25、typedef的使用

      26、为常量声明宏

      27、不要为宏定义加分号

      28、||和&&的语句执行顺序

      29、尽量用for而不是while做循环

      30、请sizeof类型而不是变量

      31、不要忽略Warning

      32、书写Debug版和Release版的程序

      ————————————————————————

      1、版权和版本

      ———————

      好的程序员会给自己的每个函数,每个文件,都注上版权和版本。

      对于C/C++的文件,文件头应该有类似这样的注释:

      /************************************************************************

      *

      * 文件名:network.c

      *

      * 文件描述:网络通讯函数集

      *

      * 创建人: Hao Chen, 2003年2月3日

      *

      * 版本号:1.0

      *

      * 修改记录:

      *

      *

      ************************************************************************/

      而对于函数来说,应该也有类似于这样的注释:

      /*================================================================

      *

      * 函 数 名:XXX

      *

      * 参 数:

      *

      * type name [IN] : descripts

      *

      * 功能描述:

      *

      * …………..

      *

      * 返 回 值:成功TRUE,失败FALSE

      *

      * 抛出异常:

      *

      * 作 者:ChenHao 2003/4/2

      *

      *

      ================================================================*/

      这样的描述可以让人对一个函数,一个文件有一个总体的认识,对代码的易读性和易维护

      性有很大的好处。这是好的作品产生的开始。

      2、缩进、空格、换行、空行、对齐

      ————————————————

      i) 缩进应该是每个程序都会做的,只要学程序过程序就应该知道这个,但是我仍然看过不

      缩进的程序,或是乱缩进的程序,如果你的公司还有写程序不缩进的程序员,请毫不犹豫

      的开除他吧,并以破坏源码罪起诉他,还要他赔偿读过他程序的人的精神损失费。缩进,

      这是不成文规矩,我再重提一下吧,一个缩进一般是一个TAB键或是4个空格。(最好用TAB

      键)

      ii) 空格。空格能给程序代来什么损失吗?没有,有效的利用空格可以让你的程序读进来

      更加赏心悦目。而不一堆表达式挤在一起。看看下面的代码:

      ha=(ha*128+*key++)%tabPtr->size;

      ha = ( ha * 128 + *key++ ) % tabPtr->size;

      有空格和没有空格的感觉不一样吧。一般来说,语句中要在各个操作符间加空格,函

      数调用时,要以各个参数间加空格。如下面这种加空格的和不加的:

      if ((hProc=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid))==NULL){

      }

      if ( ( hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) ) == NULL ){

      }

      iii) 换行。不要把语句都写在一行上,这样很不好。如:

      for(i=0;i<len;i++) if((a[i]<’0′||a[i]>’9′)&&(a[i]<’a'||a[i]>’z')) break;

      我拷,这种即无空格,又无换行的程序在写什么啊?加上空格和换行吧。

      for ( i=0; i<len; i++) {

      if ( ( a[i] < ‘0′ || a[i] > ‘9′ ) &&

      ( a[i] < ‘a’ || a[i] > ‘z’ ) ) {

      break;

      }

      }

      好多了吧?有时候,函数参数多的时候,最好也换行,如:

      CreateProcess(

      NULL,

      cmdbuf,

      NULL,

      NULL,

      bInhH,

      dwCrtFlags,

      envbuf,

      NULL,

      &siStartInfo,

      &prInfo

      );

      条件语句也应该在必要时换行:

      if ( ch >= ‘0′ || ch <= ‘9′ ||

      ch >= ‘a’ || ch <= ‘z’ ||

      ch >= ‘A’ || ch <= ‘Z’ )

      iv) 空行。不要不加空行,空行可以区分不同的程序块,程序块间,最好加上空行。如:

      HANDLE hProcess;

      PROCESS_T procInfo;

      /* open the process handle */

      if((hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid)) == NULL)

      {

      return LSE_MISC_SYS;

      }

      memset(&procInfo, 0, sizeof(procInfo));

      procInfo.idProc = pid;

      procInfo.hdProc = hProcess;

      procInfo.misc |= MSCAVA_PROC;

      return(0);

      v) 对齐。用TAB键对齐你的一些变量的声明或注释,一样会让你的程序好看一些。如:

      typedef struct _pt_man_t_ {

      int numProc; /* Number of processes */

      int maxProc; /* Max Number of processes */

      int maxProc; /* Max Number of processes */

      int numEvnt; /* Number of events */

      int maxEvnt; /* Max Number of events */

      HANDLE* pHndEvnt; /* Array of events */

      DWORD timeout; /* Time out interval */

      HANDLE hPipe; /* Namedpipe */

      TCHAR usr[MAXUSR];/* User name of the process */

      int numMsg; /* Number of Message */

      int Msg[MAXMSG];/* Space for intro process communicate */

      } PT_MAN_T;

      怎么样?感觉不错吧。

      这里主要讲述了如果写出让人赏心悦目的代码,好看的代码会让人的心情愉快,读起代码

      也就不累,工整、整洁的程序代码,通常更让人欢迎,也更让人称道。现在的硬盘空间这

      么大,不要让你的代码挤在一起,这样它们会抱怨你虐待它们的。好了,用“缩进、空格

      、换行、空行、对齐”装饰你的代码吧,让他们从没有秩序的土匪中变成一排排整齐有秩

      序的正规部队吧。

      3、程序注释

      3、程序注释

      ——————

      养成写程序注释的习惯,这是每个程序员所必须要做的工作。我看过那种几千行,却居然

      没有一行注释的程序。这就如同在公路上驾车却没有路标一样。用不了多久,连自己都不

      知道自己的意图了,还要花上几倍的时间才看明白,这种浪费别人和自己的时间的人,是

      最为可耻的人。

      是的,你也许会说,你会写注释,真的吗?注释的书写也能看出一个程序员的功底。一般

      来说你需要至少写这些地方的注释:文件的注释、函数的注释、变量的注释、算法的注释

      、功能块的程序注释。主要就是记录你这段程序是干什么的?你的意图是什么?你这个变

      量是用来做什么的?等等。

      不要以为注释好写,有一些算法是很难说或写出来的,只能意会,我承认有这种情况的时

      候,但你也要写出来,正好可以训练一下自己的表达能力。而表达能力正是那种闷头搞技

      术的技术人员最缺的,你有再高的技术,如果你表达能力不行,你的技术将不能得到充分

      的发挥。因为,这是一个团队的时代。

      好了,说几个注释的技术细节:

      i) 对于行注释(“//”)比块注释(“/* */”)要好的说法,我并不是很同意。因为一

      些老版本的C编译器并不支持行注释,所以为了你的程序的移植性,请你还是尽量使用块注

      释。

      ii) 你也许会为块注释的不能嵌套而不爽,那么你可以用预编译来完成这个功能。使用“#

      if 0”和“#endif”括起来的代码,将不被编译,而且还可以嵌套。

      4、函数的[in][out]参数

      ———————————

      我经常看到这样的程序:

      FuncName(char* str)

      {

      int len = strlen(str);

      …..

      }

      char*

      GetUserName(struct user* pUser)

      {

      return pUser->name;

      }

      不!请不要这样做。

      你应该先判断一下传进来的那个指针是不是为空。如果传进来的指针为空的话,那么,你

      的一个大的系统就会因为这一个小的函数而崩溃。一种更好的技术是使用断言(assert)

      ,这里我就不多说这些技术细节了。当然,如果是在C++中,引用要比指针好得多,但你也

      需要对各个参数进行检查。

      写有参数的函数时,首要工作,就是要对传进来的所有参数进行合法性检查。而对于传出

      的参数也应该进行检查,这个动作当然应该在函数的外部,也就是说,调用完一个函数后

      ,应该对其传出的值进行检查。

      当然,检查会浪费一点时间,但为了整个系统不至于出现“非法操作”或是“Core Dump”

      的系统级的错误,多花这点时间还是很值得的。

      5、对系统调用的返回进行判断

      ——————————————

      继续上一条,对于一些系统调用,比如打开文件,我经常看到,许多程序员对fopen返回的

      指针不做任何判断,就直接使用了。然后发现文件的内容怎么也读出不,或是怎么也写不

      进去。还是判断一下吧:

      fp = fopen(”log.txt”, “a”);

      if ( fp == NULL ){

      printf(”Error: open file error”);

      return FALSE;

      }

      其它还有许多啦,比如:socket返回的socket号,malloc返回的内存。请对这些系统调用

      返回的东西进行判断。

      6、if 语句对出错的处理

      ———————————

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

      if ( ch >= ‘0′ && ch <= ‘9′ ){

      /* 正常处理代码 */

      }else{

      /* 输出错误信息 */

      printf(”error ……”);

      return ( FALSE );

      }

      这种结构很不好,特别是如果“正常处理代码”很长时,对于这种情况,最好不要用else

      。先判断错误,如:

      if ( ch < ‘0′ || ch > ‘9′ ){

      /* 输出错误信息 */

      printf(”error ……”);

      return ( FALSE );

      }

      /* 正常处理代码 */

      ……

      这样的结构,不是很清楚吗?突出了错误的条件,让别人在使用你的函数的时候,第一眼

      就能看到不合法的条件,于是就会更下意识的避免。

      7、头文件中的#ifndef

      ——————————

      千万不要忽略了头件的中的#ifndef,这是一个很关键的东西。比如你有两个C文件,这两

      个C文件都include了同一个头文件。而编译时,这两个C文件要一同编译成一个可运行文件

      ,于是问题来了,大量的声明冲突。

      还是把头文件的内容都放在#ifndef和#endif中吧。不管你的头文件会不会被多个文件引用

      管你的头文件会不会被多个文件引用

      ,你都要加上这个。一般格式是这样的:

      #ifndef <标识>

      #define <标识>

      ……

      ……

      #endif

      <标识>在理论上来说可以是自由命名的,但每个头文件的这个“标识”都应该是唯一的。

      标识的命名规则一般是头文件名全大写,前后加下划线,并把文件名中的“.”也变成下划

      线,如:stdio.h

      #ifndef _STDIO_H_

      #define _STDIO_H_

      ……

      #endif

      (BTW:预编译有多很有用的功能。你会用预编译吗?)

      (BTW:预编译有多很有用的功能。你会用预编译吗?)

      8、在堆上分配内存

      —————————

      可能许多人对内存分配上的“栈 stack”和“堆 heap”还不是很明白。包括一些科班出身

      的人也不明白这两个概念。我不想过多的说这两个东西。简单的来讲,stack上分配的内存

      系统自动释放,heap上分配的内存,系统不释放,哪怕程序退出,那一块内存还是在那里

      。stack一般是静态分配内存,heap上一般是动态分配内存。

      由malloc系统函数分配的内存就是从堆上分配内存。从堆上分配的内存一定要自己释放。

      用free释放,不然就是术语——“内存泄露”(或是“内存漏洞”)—— Memory Leak。

      于是,系统的可分配内存会随malloc越来越少,直到系统崩溃。还是来看看“栈内存”和

      “堆内存”的差别吧。

      栈内存分配

      —————

      char*

      AllocStrFromStack()

      {

      char pstr[100];

      return pstr;

      }

      堆内存分配

      —————

      char*

      AllocStrFromHeap(int len)

      {

      char *pstr;

      if ( len <= 0 ) return NULL;

      return ( char* ) malloc( len );

      }

      对于第一个函数,那块pstr的内存在函数返回时就被系统释放了。于是所返回的char*什么

      也没有。而对于第二个函数,是从堆上分配内存,所以哪怕是程序退出时,也不释放,所

      以第二个函数的返回的内存没有问题,可以被使用。但一定要调用free释放,不然就是Mem

      ory Leak!

      在堆上分配内存很容易造成内存泄漏,这是C/C++的最大的“克星”,如果你的程序要稳定

      ,那么就不要出现Memory Leak。所以,我还是要在这里千叮咛万嘱付,在使用malloc系统

      蛑龈叮谑褂胢alloc系统

      函数(包括calloc,realloc)时千万要小心。

      记得有一个UNIX上的服务应用程序,大约有几百的C文件编译而成,运行测试良好,等使用

      时,每隔三个月系统就是down一次,搞得许多人焦头烂额,查不出问题所在。只好,每隔

      两个月人工手动重启系统一次。出现这种问题就是Memery Leak在做怪了,在C/C++中这种

      问题总是会发生,所以你一定要小心。一个Rational的检测工作——Purify,可以帮你测

      试你的程序有没有内存泄漏。

      我保证,做过许多C/C++的工程的程序员,都会对malloc或是new有些感冒。当你什么时候

      在使用malloc和new时,有一种轻度的紧张和惶恐的感觉时,你就具备了这方面的修养了。

      对于malloc和free的操作有以下规则:

      1) 配对使用,有一个malloc,就应该有一个free。(C++中对应为new和delete)

      2) 尽量在同一层上使用,不要像上面那种,malloc在函数中,而free在函数外。最好在同

      一调用层上使用这两个函数。

      3) malloc分配的内存一定要初始化。free后的指针一定要设置为NULL。

      注:虽然现在的操作系统(如:UNIX和Win2k/NT)都有进程内存跟踪机制,也就是如果你

      有没有释放的内存,操作系统会帮你释放。但操作系统依然不会释放你程序中所有产生了M

      emory Leak的内存,所以,最好还是你自己来做这个工作。(有的时候不知不觉就出现Mem

      ory Leak了,而且在几百万行的代码中找无异于海底捞针,Rational有一个工具叫Purify

      蛐械拇胫姓椅抟煊诤5桌陶耄琑ational有一个工具叫Purify

      ,可能很好的帮你检查程序中的Memory Leak)

      9、变量的初始化

      ————————

      接上一条,变量一定要被初始化再使用。C/C++编译器在这个方面不会像JAVA一样帮你初始

      化,这一切都需要你自己来,如果你使用了没有初始化的变量,结果未知。好的程序员从

      来都会在使用变量前初始化变量的。如:

      1) 对malloc分配的内存进行memset清零操作。(可以使用calloc分配一块全零的内存

      )

      2) 对一些栈上分配的struct或数组进行初始化。(最好也是清零)

      不过话又说回来了,初始化也会造成系统运行时间有一定的开销,所以,也不要对所有的

      变量做初始化,这个也没有意义。好的程序员知道哪些变量需要初始化,哪些则不需要。

      如:以下这种情况,则不需要。

      char *pstr; /* 一个字符串 */

      pstr = ( char* ) malloc( 50 );

      if ( pstr == NULL ) exit(0);

      strcpy( pstr, “Hello Wrold” );

      strcpy( pstr, “Hello Wrold” );

      但如果是下面一种情况,最好进行内存初始化。(指针是一个危险的东西,一定要初始化

      )

      char **pstr; /* 一个字符串数组 */

      pstr = ( char** ) malloc( 50 );

      if ( pstr == NULL ) exit(0);

      /* 让数组中的指针都指向NULL */

      memset( pstr, 0, 50*sizeof(char*) );

      而对于全局变量,和静态变量,一定要声明时就初始化。因为你不知道它第一次会在哪里

      被使用。所以使用前初始这些变量是比较不现实的,一定要在声明时就初始化它们。如:

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

      10、h和c文件的使用

      —————————

      —————————

      H文件和C文件怎么用呢?一般来说,H文件中是declare(声明),C文件中是define(定义

      )。因为C文件要编译成库文件(Windows下是.obj/.lib,UNIX下是.o/.a),如果别人要

      使用你的函数,那么就要引用你的H文件,所以,H文件中一般是变量、宏定义、枚举、结

      构和函数接口的声明,就像一个接口说明文件一样。而C文件则是实现细节。

      H文件和C文件最大的用处就是声明和实现分开。这个特性应该是公认的了,但我仍然看到

      有些人喜欢把函数写在H文件中,这种习惯很不好。(如果是C++话,对于其模板函数,在V

      C中只有把实现和声明都写在一个文件中,因为VC不支持export关键字)。而且,如果在H

      文件中写上函数的实现,你还得在makefile中把头文件的依赖关系也加上去,这个就会让

      你的makefile很不规范。

      最后,有一个最需要注意的地方就是:带初始化的全局变量不要放在H文件中!

      例如有一个处理错误信息的结构:

      char* errmsg[] = {

      /* 0 */ “No error”,

      /* 1 */ “Open file error”,

      /* 2 */ “Failed in sending/receiving a message”,

      /* 3 */ …

    没有评论 »

    linux c文件编程

    十月 26, 2008 | c/c++, linux | RSS 2.0

    复制

    #include <unistd.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <errno.h>
    #include <string.h>
    #include <stdlib.h>
    #define BUFFER_SIZE 1024
    int main(int argc,char **argv)
    {
      int from_fd,to_fd;
      int bytes_read,bytes_write;
      char buffer[BUFFER_SIZE];
      char *ptr;
      if(argc!=3)
      {
        fprintf(stderr,”Usage:%s fromfile tofilea”,argv[0]);
        exit(1);
      }
      /* 打开源文件 */
      if((from_fd=open(argv[1],O_RDONLY))==-1)
      {
        fprintf(stderr,”Open %s Error:%s”,argv[1],strerror(errno));
        exit(1);
      }
      /* 创建目的文件 */
      if((to_fd=open(argv[2],O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR))==-1)
      {
        fprintf(stderr,”Open %s Error:%s”,argv[2],strerror(errno));
        exit(1);
      }
      /* 以下代码是一个经典的拷贝文件的代码 */
      while(bytes_read=read(from_fd,buffer,BUFFER_SIZE))
      {
        /* 一个致命的错误发生了 */
        if((bytes_read==-1)&&(errno!=EINTR)) break;
        else if(bytes_read>0)
        {
          ptr=buffer;
          while(bytes_write=write(to_fd,ptr,bytes_read))
          {
            /* 一个致命错误发生了 */
            if((bytes_write==-1)&&(errno!=EINTR))break;
            /* 写完了所有读的字节 */
            else if(bytes_write==bytes_read) break;
            /* 只写了一部分,继续写 */
            else if(bytes_write>0)
            {
              ptr+=bytes_write;
              bytes_read-=bytes_write;
            }
          }
          /* 写的时候发生的致命错误 */
          if(bytes_write==-1)break;
        }
      }
      close(from_fd);
      close(to_fd);
      exit(0);
    }
    目录查看

    #include <unistd.h>
    #include <stdio.h>
    #include <errno.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <dirent.h>
    #include <time.h>
    static int get_file_size_time(const char *filename)
    {
      struct stat statbuf;
      if(stat(filename,&statbuf)==-1)
      {
        printf(”Get stat on %s Error:%s”,
            filename,strerror(errno));
        return(-1);
      }
      if(S_ISDIR(statbuf.st_mode))return(1);
      if(S_ISREG(statbuf.st_mode))
        printf(”%s size:%ld bytestmodified at %s”,
            filename,statbuf.st_size,ctime(&statbuf.st_mtime));

      return(0);
    }
    int main(int argc,char **argv)
    {
      DIR *dirp;
      struct dirent *direntp;
      int stats;
      if(argc!=2)
      {
        printf(”Usage:%s filenamea”,argv[0]);
        exit(1);
      }
      if(((stats=get_file_size_time(argv[1]))==0)||(stats==-1))exit(1);
      if((dirp=opendir(argv[1]))==NULL)
      {
        printf(”Open Directory %s Error:%s”,
            argv[1],strerror(errno));
        exit(1);
      }
      while((direntp=readdir(dirp))!=NULL)
        if(get_file_size_time(direntp->d_name)==-1)break;
      closedir(dirp);
      exit(1);
    }
    管道
    include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <errno.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #define BUFFER 255
    int main(int argc,char **argv)
    {
      char buffer[BUFFER+1];
      int fd[2];
      if(argc!=2)
      {
        fprintf(stderr,”Usage:%s stringa”,argv[0]);
        exit(1);
      }
      if(pipe(fd)!=0)
      {
        fprintf(stderr,”Pipe Error:%sa”,strerror(errno));
        exit(1);
      }
      if(fork()==0)
      {
        close(fd[0]);
        printf(”Child[%d] Write to pipea”,getpid());
        snprintf(buffer,BUFFER,”%s”,argv[1]);
        write(fd[1],buffer,strlen(buffer));
        printf(”Child[%d] Quita”,getpid());
        exit(0);
      }
      else
      {
        close(fd[1]);
        printf(”Parent[%d] Read from pipea”,getpid());
        memset(buffer,’0′,BUFFER+1);
        read(fd[0],buffer,BUFFER);
        printf(”Parent[%d] Read:%s”,getpid(),buffer);
        exit(1);
      }
    }

    没有评论 »

    段错误?

    十月 25, 2008 | c/c++, linux | RSS 2.0

    #include <stdio.h>
    #include <stdlib.h>
    #define N 13
    typedef struct node {
      int code;
      struct node *next;

    }NODE,*LinkList;
    LinkList create_list(int n) {
      LinkList head,p;
      int i;
      head = (NODE *)malloc(sizeof(NODE));
      if(!head) {
        printf(”memor allocation is fail”);
        exit(1);
      }
      head->code = 1;
      head->next = head;
      for(i =n ;i > 1; i++) {
        p = (NODE *)malloc(sizeof(NODE));
        if(!p) {

          printf(”memor allocation is fail”);

          exit(1);
        }
        p->code = i;
        p->next = head->next;
        head->next = p;

      }
      return head;
    }

    void output(LinkList head) {
      LinkList p;
      p = head;
      do {
        printf(”%4d”,p->code);
        p = p->next;
      }while(p != head);

      printf(”");

    }
    void play(LinkList head, int n) {
      LinkList p,q;
      int c = 0,k;
      p = head;c=1;k=n;
      while(k > 1) {
        if(c == 2) {
          q = p->next;
          p->next = q->next;
          printf(”%4d”,q->code);
          free(q);
          c = 0;
          k–;

        }else {
          c++;p=p->next;
        }

      }

      printf(”%4dwas the winer”,p->code);

    }
    int main() {
      LinkList head;
      int n;
      printf(”please input your number”);
      scanf(”%d”,&n);
      output(head);
      play(head,n);

      return 0;
    }

    没有评论 »

    经典的段错误可恶的\”Segmentation faults\”

    十月 25, 2008 | c/c++, linux, 数学 | RSS 2.0

    背景

        最近一段时间在linux下用C做一些学习和开发,但是由于经验不足,问题多多。而段错误就是让我非常头痛的一个问题。不过,目前写一个一千行左右的代码,也很少出现段错误,或者是即使出现了,也很容易找出来,并且处理掉。

        那什么是段错误?段错误为什么是个麻烦事?以及怎么发现程序中的段错误以及如何避免发生段错误呢?

       一方面为了给自己的学习做个总结,另一方面由于至今没有找到一个比较全面介绍这个虽然是“FREQUENTLY ASKEDQUESTIONS”的问题,所以我来做个抛砖引玉吧。下面就从上面的几个问题出发来探讨一下“Segmentation faults”吧。

    目录

    1。什么是段错误?
    2。为什么段错误这么“麻烦”?
    3。编程中通常碰到段错误的地方有哪些?
    4。如何发现程序中的段错误并处理掉?

    正文

    1。什么是段错误?

    下面是来自Answers.com的定义:

    Asegmentation fault (often shortened to segfault) is a particular errorcondition that can occur during the operation of computer software. Inshort, a segmentation fault occurs when a program attempts to access amemory location that it is not allowed to access, or attempts to accessa memory location in a way that is not allowed (e.g., attempts to writeto a read-only location, or to overwrite part of the operating system).Systems based on processors like the Motorola 68000 tend to refer tothese events as Address or Bus errors.

    Segmentation is oneapproach to memory management and protection in the operating system.It has been superseded by paging for most purposes, but much of theterminology of segmentation is still used, “segmentation fault” beingan example. Some operating systems still have segmentation at somelogical level although paging is used as the main memory managementpolicy.

    On Unix-like operating systems, a process that accessesinvalid memory receives the SIGSEGV signal. On Microsoft Windows, aprocess that accesses invalid memory receives theSTATUS_ACCESS_VIOLATION exception.

    另外,这里有个基本上对照的中文解释,来自http://www.linux999.org/html_sql/3/132559.htm

    所谓的段错误就是指访问的内存超出了系统所给这个程序的内存空间,通常这个值是由gdtr来保存的,他是一个48位的寄存器,其中的32位是保存由它指向的gdt表,后13位保存相应于gdt的下标,最后3位包括了程序是否在内存中以及程序的在cpu中的运行级别,指向的gdt是由以64位为一个单位的表,在这张表中就保存着程序运行的代码段以及数据段的起始地址以及与此相应的段限和页面交换还有程序运行级别还有内存粒度等等的信息。一旦一个程序发生了越界访问,cpu就会产生相应的异常保护,于是segmentation fault就出现了

    通过上面的解释,段错误应该就是访问了不可访问的内存,这个内存区要么是不存在的,要么是受到系统保护的。

    2。为什么段错误这么麻烦?

    中国linux论坛有一篇精华帖子《Segment fault 之永远的痛》(http://www.linuxforum.net/forum/gshowflat.php?Cat=&Board=program&Number=193239&page=2&view=collapsed&sb=5&o=all&fpart=1&vc=1)
    在主题帖子里头,作者这么写道:

    写程序好多年了,Segment fault 是许多C程序员头疼的提示。指针是好东西,但是随着指针的使用却诞生了这个同样威力巨大的恶魔。

    Segment fault 之所以能够流行于世,是与Glibc库中基本所有的函数都默认型参指针为非空有着密切关系的。

    不知道什么时候才可以有能够处理NULL的glibc库诞生啊!

    不得已,我现在为好多的函数做了衣服,避免glibc的函数被NULL给感染,导致我的Mem访问错误,而我还不知道NULL这个病毒已经在侵蚀我的身体了。

    Segment fault 永远的痛……

        后面有好多网友都跟帖了,讨论了Segmentation faults为什么这么“痛”,尤其是对于服务器程序来说,是非常头痛的,为了提高效率,要尽量减少一些不必要的段错误的“判断和处理”,但是不检查又可能会存在段错误的隐患。

        那么如何处理这个“麻烦”呢?
        就像人不可能“完美”一样,由人创造的“计算机语言“同样没有“完美”的解决办法。
        我们更好的解决办法也许是:

        通过学习前人的经验和开发的工具,不断的尝试和研究,找出更恰当的方法来避免、发现并处理它。对于一些常见的地方,我们可以避免,对于一些“隐藏”的地方,我们要发现它,发现以后就要及时处理,避免留下隐患。

        下面我们可以通过具体的实验来举出一些经常出现段错误的地方,然后再举例子来发现和找出这类错误藏身之处,最后处理掉。

    3。编程中通常碰到段错误的地方有哪些?

    为了进行下面的实验,我们需要准备两个工具,一个是gcc,一个是gdb
    我是在ubuntu下做的实验,安装这两个东西是比较简单的

    sudo apt-get install gcc-4.0 libc6-dev
    sudo apt-get install gdb

    好了,开始进入我们的实验,我们粗略的分一下类

    1)往受到系统保护的内存地址写数据

        有些内存是内核占用的或者是其他程序正在使用,为了保证系统正常工作,所以会受到系统的保护,而不能任意访问。

    例子1:

    Code:
    #include <stdio.h>intmain(){        int i = 0;        scanf (”%d”, i);  /* should have used &i */        printf (”%d”, i);        return 0;}
    [Ctrl+A Select All]
        编译和执行一下

    $ gcc -o segerr segerr.c
    $ ./segerr
    10
    段错误

        咋一看,好像没有问题哦,不就是读取一个数据然后给输出来吗?

    下面我们来调试一下,看看是什么原因?

    $ gcc -g -o segerr segerr.c        –加-g选项查看调试信息
    $ gdb ./segerr
    (gdb) l                    –用l(list)显示我们的源代码
    1       #include <stdio.h>
    2
    3       int
    4       main()
    5       {
    6               int i = 0;
    7
    8               scanf (”%d”, i);  /* should have used &i */
    9               printf (”%d”, i);
    10              return 0;
    (gdb) b 8                –用b(break)设置断点
    Breakpoint 1 at 0×80483b7: file segerr.c, line 8.
    (gdb) p i                –用p(print)打印变量i的值[看到没,这里i的值是0哦]
    $1 = 0

    (gdb) r                    –用r(run)运行,直到断点处
    Starting program: /home/falcon/temp/segerr

    Breakpoint 1, main () at segerr.c:8
    8               scanf (”%d”, i);  /* should have used &i */ –[试图往地址0处写进一个值]
    (gdb) n                    –用n(next)执行下一步
    10

    Program received signal SIGSEGV, Segmentation fault.
    0xb7e9a1ca in _IO_vfscanf () from /lib/tls/i686/cmov/libc.so.6
    (gdb) c            –在上面我们接收到了SIGSEGV,然后用c(continue)继续执行
    Continuing.

    Program terminated with signal SIGSEGV, Segmentation fault.
    The program no longer exists.
    (gdb) quit        –退出gdb

    果然
    我们“不小心”把&i写成了i
    而我们刚开始初始化了i为0,这样我们不是试图向内存地址0存放一个值吗?实际上很多情况下,你即使没有初始化为零,默认也可能是0,所以要特别注意。

    补充:
    可以通过man 7 signal查看SIGSEGV的信息。

    $ man 7 signal | grep SEGV
    Reformatting signal(7), please wait…
           SIGSEGV      11       Core    Invalid memory reference

    例子2:

    Code:
    #include <stdio.h>intmain(){        char *p;        p = NULL;        *p = ‘x’;        printf(”%c”, *p);        return 0;}
    [Ctrl+A Select All]
    很容易发现,这个例子也是试图往内存地址0处写东西。

    这里我们通过gdb来查看段错误所在的行

    $ gcc -g -o segerr segerr.c
    $ gdb ./segerr
    (gdb) r        –直接运行,我们看到抛出段错误以后,自动显示出了出现段错误的行,这就是一个找出段错误的方法
    Starting program: /home/falcon/temp/segerr

    Program received signal SIGSEGV, Segmentation fault.
    0×08048516 in main () at segerr.c:10
    10              *p = ‘x’;
    (gdb)

    2)内存越界(数组越界,变量类型不一致等)

    例子3:
    Code:
    #include <stdio.h>intmain(){        char test[1];        printf(”%c”, test[1000000000]);        return 0;}
    [Ctrl+A Select All]
    这里是比较极端的例子,但是有时候可能是会出现的,是个明显的数组越界的问题
    或者是这个地址是根本就不存在的

    例子4:
    Code:
    #include <stdio.h>intmain(){        int b = 10;        printf(”%s”, b);        return 0;}
    [Ctrl+A Select All]
    我们试图把一个整数按照字符串的方式输出出去,这是什么问题呢?
    由于还不熟悉调试动态链接库,所以
    我只是找到了printf的源代码的这里

    声明部分:
        int pos =0 ,cnt_printed_chars =0 ,i ;
      unsigned char *chptr ;
      va_list ap ;
    %s格式控制部分:
    case ’s’:
          chptr =va_arg (ap ,unsigned char *);
          i =0 ;
          while (chptr [i ])
          {…
              cnt_printed_chars ++;
              putchar (chptr [i ++]);
      }

    仔细看看,发现了这样一个问题,在打印字符串的时候,实际上是打印某个地址开始的所有字符,但是当你想把整数当字符串打印的时候,这个整数被当成了一个地址,然后printf从这个地址开始去打印字符,直到某个位置上的值为0。所以,如果这个整数代表的地址不存在或者不可访问,自然也是访问了不该访问的内存——segmentation fault。

    类似的,还有诸如:sprintf等的格式控制问题
    比如,试图把char型或者是int的按照%s输出或存放起来,如:
    Code:
    #include <stdio.h> #include <string.h>char c=’c';int i=10;char buf[100];printf(”%s”, c);        //试图把char型按照字符串格式输出,这里的字符会解释成整数,再解释成地址,所以原因同上面那个例子printf(”%s”, i);            //试图把int型按照字符串输出memset(buf, 0, 100);sprintf(buf, “%s”, c);    //试图把char型按照字符串格式转换memset(buf, 0, 100);sprintf(buf, “%s”, i);   //试图把int型按照字符串转换
    [Ctrl+A Select All]

    3)其他

    其实大概的原因都是一样的,就是段错误的定义。但是更多的容易出错的地方就要自己不断积累,不段发现,或者吸纳前人已经积累的经验,并且注意避免再次发生。

    例如:

    <1>定义了指针后记得初始化,在使用的时候记得判断是否为NULL
    <2>在使用数组的时候是否被初始化,数组下标是否越界,数组元素是否存在等
    <3>在变量处理的时候变量的格式控制是否合理等

    再举一个比较不错的例子:

    我在进行一个多线程编程的例子里头,定义了一个线程数组
    #define THREAD_MAX_NUM
    pthread_t thread[THREAD_MAX_NUM];
    用pthread_create创建了各个线程,然后用pthread_join来等待线程的结束

    刚开始我就直接等待,在创建线程都成功的时候,pthread_join能够顺利等待各个线程结束,但是一旦创建线程失败,那用pthread_join来等待那个本不存在的线程时自然会存在访问不能访问的内存的情况,从而导致段错误的发生,后来,通过不断调试和思考,并且得到网络上资料的帮助,找到了上面的原因和解决办法:

    在创建线程之前,先初始化我们的线程数组,在等待线程的结束的时候,判断线程是否为我们的初始值
    如果是的话,说明我们的线程并没有创建成功,所以就不能等拉。否则就会存在释放那些并不存在或者不可访问的内存空间。

    上面给出了很常见的几种出现段错误的地方,这样在遇到它们的时候就容易避免拉。但是人有时候肯定也会有疏忽的,甚至可能还是会经常出现上面的问题或者其他常见的问题,所以对于一些大型一点的程序,如何跟踪并找到程序中的段错误位置就是需要掌握的一门技巧拉。

    4。如何发现程序中的段错误?

    有个网友对这个做了比较全面的总结,除了感谢他外,我把地址弄了过来。文章名字叫《段错误bug的调试》(http://www.cublog.cn/u/5251/showart.php?id=173718),应该说是很全面的。

    而我常用的调试方法有:

    1)在程序内部的关键部位输出(printf)信息,那样可以跟踪 段错误 在代码中可能的位置

    为了方便使用这种调试方法,可以用条件编译指令#ifdef DEBUG和#endif把printf函数给包含起来,编译的时候加上-DDEBUG参数就可以查看调试信息。反之,不加上该参数进行调试就可以。

    2)用gdb来调试,在运行到段错误的地方,会自动停下来并显示出错的行和行号

    这个应该是很常用的,如果需要用gdb调试,记得在编译的时候加上-g参数,用来显示调试信息,对于这个,网友在《段错误bug的调试》文章里创造性的使用这样的方法,使得我们在执行程序的时候就可以动态扑获段错误可能出现的位置:通过扑获SIGSEGV信号来触发系统调用gdb来输出调试信息。如果加上上面提到的条件编译,那我们就可以非常方便的进行段错误的调试拉。

    3)还有一个catchsegv命令
    通过查看帮助信息,可以看到

    Catch segmentation faults in programs

    这个东西就是用来扑获段错误的,它通过动态加载器(ld-linux.so)的预加载机制(PRELOAD)把一个事先写好的库(/lib/libSegFault.so)加载上,用于捕捉断错误的出错信息。

    到这里,“初级总结篇”算是差不多完成拉。欢迎指出其中表达不当甚至错误的地方,先谢过!

    参考资料[具体地址在上面的文章中都已经给出拉]:

    1。段错误的定义
    Ansers.com
    http://www.answers.com
    Definition of “Segmentation fault”
    http://www.faqs.org/qa/qa-673.html
    2。《什么是段错误》
    http://www.linux999.org/html_sql/3/132559.htm
    3。《Segment fault 之永远的痛》
    http://www.linuxforum.net/forum/gshowflat.php?Cat=&Board=program&Number=193239&page=2&view=collapsed&sb=5&o=all&fpart=
    4。《段错误bug的调试》
    http://www.cublog.cn/u/5251/showart.php?id=173718

    后记

    虽然感觉没有写什么东西,但是包括查找资料和打字,也花了好些几个小时,不过总结一下也是值得的,欢迎和我一起交流和讨论,也欢迎对文章中表达不当甚至是错误的地方指正一下。

    我们在用C/C++语言写程序的时侯,内存管理的绝大部分工作都是需要我们来做的。实际上,内存管理是一个比较繁琐的工作,无论你多高明,经验多丰富,难免会在此处犯些小错误,而通常这些错误又是那么的浅显而易于消除。但是手工“除虫”(debug),往往是效率低下且让人厌烦的,本文将就”段错误”这个内存访问越界的错误谈谈如何快速定位这些”段错误”的语句。
    下面将就以下的一个存在段错误的程序介绍几种调试方法:

         1  dummy_function (void)
         2  {
         3          unsigned char *ptr = 0×00;
         4          *ptr = 0×00;
         5  }
      …

    没有评论 »

    我的vi及其linux命令

    十月 25, 2008 | tools | RSS 2.0

    :%s/r//g 删除dos方式的回车^m 
    :%s= *$== 删除行尾空白 

    :%s/^(.*)n1/1$/ 删除重复行 

    :%s/^.{-}pdf/new.pdf/ 只是删除第1个pdf 

    :%s/<!–_.{-}–>// 又是删除多行注释(咦?为什么要说“又”呢?) 

    :g/s*^$/d 删除所有空行 :这个好用有没有人用过还有其他的方法吗?
    :g!/^dd/d 删除不含字符串dd的行 
    :v/^dd/d 同上 (译释:v == g!,就是不匹配!) 

    :g/str1/,/str2/d 删除所有第1个含str1到第1个含str2之间的行 

    :v/./.,/./-1join 压缩空行 
    :g/^$/,/./-j 压缩空行 

    ndw 或 ndw 删除光标处开始及其后的 n-1 个字符. 
    d0 删至行首. 
    d$ 删至行尾. 
    ndd 删除当前行及其后 n-1 行. 
    x 或 x 删除1个字符. 
    ctrl+u 删除输入方式下所输入的文本. 
    ^r 恢复u的操作 
    j 把下1行合并到当前行尾 
    v 选择1行 
    ^v 按下^v后即可进行矩形的选择了 
    aw 选择单词 
    iw 内部单词(无空格) 
    as 选择句子 
    is 选择句子(无空格) 
    ap 选择段落 
    ip 选择段落(无空格) 
    d 删除到行尾 
    x,y 删除与复制包含高亮区 

    dl 删除当前字符(与x命令功能相同) 
    d0 删除到某1行的开始位置 
    d^ 删除到某1行的第1个字符位置(不包括空格或tab字符) 
    dw 删除到某个单词的结尾位置 
    d3w 删除到第3个单词的结尾位置 
    db 删除到某个单词的开始位置 
    dw 删除到某个以空格作为分隔符的单词的结尾位置 
    db 删除到某个以空格作为分隔符的单词的开始位置 
    d7b 删除到前面7个以空格作为分隔符的单词的开始位置 
    d) 删除到某个语句的结尾位置 
    d4) 删除到第4个语句的结尾位置 
    d( 删除到某个语句的开始位置 
    d) 删除到某个段落的结尾位置 
    d{ 删除到某个段落的开始位置 
    d7{ 删除到当前段落起始位置之前的第7个段落位置 
    dd 删除当前行 
    d/text 删除从文本中出现“text”中所指定字样的位置, 
    1直向前直到下1个该字样所出现的位置(但不包括该字样)之间的内容 
    dfc 删除从文本中出现字符“c”的位置,1直向前直到下1个该字符所出现的位置(包括该字符)之间的内容 
    dtc 删除当前行直到下1个字符“c”所出现位置之间的内容 
    d 删除到某1行的结尾 
    d$ 删除到某1行的结尾 
    5dd 删除从当前行所开始的5行内容 
    dl 删除直到屏幕上最后1行的内容 
    dh 删除直到屏幕上第1行的内容 
    dg 删除直到工作缓存区结尾的内容 
    d1g 删除直到工作缓存区开始的
    缩进多行
    1、按v进入visual状态,选择多行,用>或<缩进或缩出
    2、将光标移动到要移动的块的第1行行首,再按n>j,n为要缩进的行数
    3、如果编辑的是代码文件,可以将光标放在{或者}上,然后按=%,vim将自动将里面的代码块进行缩进
    复制多行
    命令行模式下输入
    :5,10 co 12
    光标移到起始行,输入ma
    光标移到结束行,输入mb
    光标移到粘贴行,输入mc
    移动多行
    :5,10move12
    :5,10m12
    然后 :’a,’b co ‘c

    光标放到第5行,
    输入:y6y
    光标放到第12行,
    输入:p
    查找目录下包含某个文件

    find   yourdir   -type   f   -name   “*”   |   xargs   grep   yourstring
    备份
    ls | xargs -i cp {} {}.bak
    计算文件第1行的字数

    cat yourfile |xargs -i echo echo ‘{}’|wc -c|sh
    2008年12月10日13:52:29
    vi的帮助
    1 :help is your friend;
    查询模式
    :help i_;help v_ ;
    查询某个命令:
    :help x;
    :help zf;
     ctrl+] 跳到标志处 ctrl + t 跳回去
    2 vi的位置及其移动:
    大致位置:ctrl+g
    详细位置:gctrl+g
    set runler
    gg,g
    ng,ngg
    n%
    标志位:ma,mb
    ‘<letter>跳转到标志位
    、<leter>
    查看你现在定义的标志位:marks
    c’<leter>改变到标志位
    d’<lette>
    =’<letter>格式化1段文字,到标志位
    gg=vg对全文进行格式化
    ”在两个地方移动
    ‘.跳转到最后修改的地方
    ctrl+o,ctrl+t在历史记录移动
    ctrl-o    跳到上1位置
    ctrl-i    跳到下1位置
    ctrl-y    向上滚屏
    ctrl-e    向下滚屏
    ctrl-u    向上滚半屏
    ctrl-d    向下滚半屏
    ctrl-b    向上滚1屏
    ctrl-f    向下滚1屏
    3 vi的插入
    好的习惯:
    ctrl+y,ctrl+e
    ctrl+a对1行中的数字进行自加
    查找
    f/f,t/t,df;df;删除从鼠标到第1个;的部分
    *#查找这个单词
    g*#查找这个单词的部分
    [i列出鼠标下的单词
    / ?搜索
    d/^#删除从鼠标到#位置的部分
    1 vip 选择1段文字进入虚拟模式,没有空白
    文本
     das 删除1个句子,包括空白
    拆分窗口
    new    打开新的空窗口
    split    水平拆分窗口
    vsplit    垂直拆分窗口
    split socket.c    在新窗口中打开socket.c文件
    5ctrl-w+    将窗口高度增加5行
    10ctrl-w-    将窗口高度减少10行
    ctrl-wctrl-w    在窗口间切换
    :set ignorecase    设置忽略大小写
    :set noignorecase    不忽略大小写

    linux批量替换多个文件中字符串

    用sed命令可以批量替换多个文件中的字符串.
    sed -i "s/原字符串/新字符串/g" `grep 原字符串 -rl 所在目录`
    例如:我要把mahuinan替换为huinanma,执行命令:
    sed -i "s/mahuinan/huinanma/g" 'grep mahuinan -rl /www'
    这是目前linux最简单的批量替换字符串命令了!
    具体格式如下:
    sed -i "s/oldstring/newstring/g" `grep oldstring -rl /path`
    实例代码:
    sed -i "s/大小多少/日月水火/g" `grep 大小多少 -rl /usr/aa`
    sed -i "s/大小多少/日月水火/g" `grep 大小多少 -rl ./`
    sed单行命令可以和find, xargs结合起来,完成对多个文件的文本替换.如
    find . -name "*.[ch]” -exec sed -i ’s/t/    /g’ {} ;
    或者 find . -name “*.[c|h]” | xargs sed -i ’s/t/   /g’
    都可以把当前目录下的.c .h文件中的tab替换成4个空格.
    sed提供的域提取功能可以进行比较复杂的替换如
    “sed ’s/(//*)(.*)//*2*//‘  test.c”
    将把test.c中所有的如”//comment” “int a; //comment” “//////////////////”替换成”/*comment*/” “int a; /*comment*/” “/**/”(因为严格ascii c 只允许这种格式的注释)

    批量替换文件中的字符:
    find . -name “space.share.*” | xargs sed -i ’s/320/200/g’
    find . -name “^space.share.*” | xargs sed -i ’s/200/share_pic_width/g’

    替换1段程序中的行号:
    :%s/[0-9]{1,2}//;
    :%s/^[[:space:]]+//
    :%s/^[[:space:]]+[0-9]{1,2}//

    2008年12月11日18:04:53
    插入模式下,自动补全:
    ctrl+p向后搜索
    ctrl+n上前搜索
    补全文件名
    ctrl+x,ctrl+f
    程序源码自动补全
    ctrl+x,ctrl+o;
    重复上1次插入模式下的内容
    ctrl+a
    从其他行复制
    ctrl+y;
    ctrl+r插入寄存器的内容
    对齐文本
    1,2left30

     增加注释(1个操作应用在多行)
    比如需要增加#或者是//这种注释:
    ctrl + v 定位到开始行,然后选定需要的行,然后执行 i 命令,然后输入 # 或 //,然后按esc键两次,即可把注释操作应用到所有选定的行,记住选定不能使用v指令,而应该使用ctrl + v(清除注释请参考上面的方法)(v是按行选定,ctrl + v 是按照列选定)

     对齐行
    v 选定需要整齐的行,输入 = 进行归整

       :g/^/exec “s/^/”.strpart(line(”.”).” “, 0, 4)   在行首插入行号
       :runtime! syntax/2html.vim                      转换 txt 成 html, 会按照你的

      <<                  输入此命令则光标所在行向左移动1个 tab.
      >>                  输入此命令则光标所在行向右移动1个 tab.
      5>>                 输入此命令则光标后 5 行向右移动1个 tab.
      :12,24>             此命令将12行到14行的数据都向右移动1个 tab.
      :12,24>>            此命令将12行到14行的数据都向右移动两个 tab.

    替换多个文件内容的东西

          1 #!/bin/bash
          2
          3
          4 for i in `ls space.*`
          5 do
          6     sed “s|<!–{if !empty($this->_visiteduser->_gender)&&($this->_visiteduser->_gender)==’m'}–>他<!–{else}–>她<!-  -{/if}–>|{1}|g” $i > $i-new
          7     #mv $i $i-old
          8     mv $i-new $i
          9 done
    继续学习vi

    寄存器操作
    “?nyy:将当前行及其下n行的内容保存到寄存器?中,其中?为1个字母,n为1个数字
    “?nyw:将当前行及其下n个字保存到寄存器?中,其中?为1个字母,n为1个数字
    “?nyl:将当前行及其下n个字符保存到寄存器?中,其中?为1个字母,n为1个数字
    “?p:取出寄存器?中的内容并将其放到光标位置处.这里?可以是1个字母,也可以是1个数字
    ndd:将当前行及其下共n行文本删除,并将所删内容放到1号删除寄存器中.
    淫技
    2. 清除页面中所有行尾的空白符:
    :%s/s+$//

    3. 清除所有空白
    :%s/(s*)+//

    4. 去掉所有的//注释
    :%s!s*//.*!!

    5. 去掉所有的/* */注释
    :%s!s*/*_.{-}*/s*!
    【9】窗口操作
    :vne [filename]
    :sp [filename]
    :s [filename]
    :new [filename]
    :^w + ^r 交换两个窗口的位置
    ^w = 窗口等宽
    :res -n 窗口高度减小n
    :res +n 窗口高度增大n
    :vert res -n
    :vert res +n
    【12】删除操作
    :%s/r//g 删除dos方式的回车^m
    :%s= *$== 删除行尾空白
    :%s/^(.*)n1/1$/ 删除重复行
    :%s/^.pdf/new.pdf/ 只是删除第1个pdf
    :%s/<!–_.–>// 又是删除多行注释(咦?为什么要说“又”呢?)
    :g/^s*$/d 删除所有空行
    :g!/^dd/d 删除不含字符串’dd’的行
    :v/^dd/d 同上 (译释:v == g!,就是不匹配!)
    :g/str1/,/str2/d 删除所有第1个含str1到第1个含str2之间的行

    :v/./.,/./-1join 压缩空行
    :g/^$/,/./-j 压缩空行
    【15】大小写转换
    guu 行小写
    guu 行大写
    g~~ 行翻转(当然指大小写啦)
    guw 字小写(狭义字) 译注:建议对比iw
    guw 字大写(狭义字)
    g~w 字翻转(狭义字)
    veu 字大写(广义字)
    ve~ 字翻转(广义字)
    gggug 把整个文章全部小写(ft!bt!)

    【16】 跳转足迹’. 跳到最后修改的那1行 (超级有用)(ft,怎么又是这个评价)
    `. 不仅跳到最后修改的那1行,还要定位到修改点
    <c-o> 依次沿着你的跳转记录向回跳 (从最近的1次开始)
    <c-i> 依次沿着你的跳转记录向前跳
    :ju(mps) 列出你跳转的足迹
    列块操作
    <c-v>选中列块
    i<string><esc> 在块的每1行首加上string
    a<string><esc> 在块的每1行尾加上string
    c<string><esc> 修改选中的列块(即删除并输入新内容 )
    自动补齐
    ctrl-x ctrl-f 文件名
    ctrl-x ctrl-l 整行
    ctrl-x ctrl-d 宏定义 (并搜索所包含的文件)
    ctrl-x ctrl-i 当前文件以及所包含的文件
    ctrl-x ctrl-k 词典文件内的单词
    ctrl-x ctrl-t 近义词词典文件内的单词
    ctrl-x ctrl-] 标记
    ctrl-x ctrl-v vim 命令行
    c<string><esc> 修改选中的列块(即删除并输入新内容 )
    “””””””””””::自动加注释的*星号””””””””””””””””””””””””””””””””””””””””””””””””””

    vi扩展的正则表达式
    | 指示交替,如home|house 指匹配home或house
    () 用于分组,如home|house可以写为ho(me|use)
    *可以用到()上(home|house)* 可以匹配home, homehouse,househome, homehousehomehouse
    (…) 实际匹配的文本可以使用、等在替换命令的替换部分进行检索
    + 可匹配正则表达式的1次或多次出现,既可以是单个字符也可以是()或(…)括起的内容,至少匹配1次
    ho(use|me)+ 指至少是house或home不允许是空
    ? 指前面正则表达式的0次或1次出现,表示出现或未出现.如free?d 将匹配fred或freed除此不能匹配其他文本

    定义区间表达式.区间表达式描述了表示重复次数的计数数字. 同
    (home|house) 只能匹配homehome, homehouse, househome, househouse   

    ///////////////// 记1条复复杂的命令

    grep -i ‘china’ 01-www-access-log | grep -vi ‘welcome2′ | grep -vi ’spider’ | grep -vi ‘yahoo’| grep -vi ‘head’ | awk ‘{print $1}’|sort | uniq -c | sort -n | wc -l

    shell
    dir=$1
    files=`find $dir -type f`
    for file in $files
    do
        dir_name=`dirname $file`
        ori_filename=`basename $file`
        
        new_filename=`echo $ori_filename | tr [:upper:] [:lower:]` > /dev/null
       
        mv $dir_name/$ori_filename $dir_name/$new_filename
    done

    比如说:19******/01/03,改写以后是:03/01/19******
    sed ’s/([0-9]{4})/([0-9]{2})/([0-9]{2})/3/2/1/g’ date.txt
    删除文件中含有”[ ]“的行,中括号之间有1个空格
    sed ‘/[ ]/d’ kuohao
    模式空间的正则表达式为 modules/([^/ ]*)/.*

    (…)
    在此处表示确定…的内容将会被存放到1个地址中,以后想得到此出地址可用x (x取决于之前是否有起同样作用的()出现,如果有1个,那么x=2,如果没有x=1,以此类推)来获得

    / 转义 / (反斜杠)

    ([^/ ]*) 匹配 [^/ ]*

    [^/ ]表示 非/ 字符,[^/]表示 非/ 字符重复0或多次.

    .* 匹配1串任意字符
    $ sed -e ‘/abcde/{n;s// /}’ hebing.txt

    #!/bin/sh
    home_dir=/home/ibmtc/personal
    pid=1001
    while read line;
    do
        #user name is lower
        name=`echo $line | tr [:upper:] [:lower:]`
        #but the original directory is upper
        echo $name:x:$pid:$pid::$home_dir/$line:/sbin/nologin >> usersfile
        let “pid+=1″
    done

    for name in $@
    do
        sed -i “/$name/s//sbin/nologin//bin/sh/” usersfile
    done

    这里,利用1个变量来作为pattern,直接的使用
    sed -i ‘/$name/s//sbin/nologin//bin/sh/’ usersfile
    是不行的

    感谢linuxsir上的热心网友回答我的问题,找到了解决方法.

    sed -i '/'$name'/s//sbin/nologin//bin/sh/' usersfilesed -i "/$name/s//sbin/nologin//bin/sh/" usersfile

    find $dir -atime 7 -print0 | xargs -r0 rm–

    find-path “/path/dir” -prune -o -print :
    find-path /path/dir -prune -o -print
    是1样的效果
    find( -path “/path/dir1″ -o -path”/path/dir2″ ) -prune -o-print
    :
    find( -path /path/dir1″ -o -path”/path/dir2 ) -prune -o-print
    是1样的,

    没有评论 »

    vi

    十月 25, 2008 | tools, 数据结构算法 | RSS 2.0

    缩进多行
    1、按v进入visual状态,选择多行,用>或<缩进或缩出
    2、将光标移动到要移动的块的第一行行首,再按n>j,n为要缩进的行数
    3、如果编辑的是代码文件,可以将光标放在{或者}上,然后按=%,vim将自动将里面的代码块进行缩进
    复制多行
    命令行模式下输入
    :5,10 co 12
    光标移到起始行,输入ma
    光标移到结束行,输入mb
    光标移到粘贴行,输入mc

    然后 :’a,’b co ‘c

    光标放到第五行,
    输入:y6y
    光标放到第12行,
    输入:p

    没有评论 »

    更好得释放空间得方法

    十月 25, 2008 | 网站架构 | RSS 2.0

    是的。有些早期的 malloc() 文档提到释放的内存中的内容会 “保留”, 但这个欠考虑的保证并不普遍而且也不是 C 标准要求的。

    几乎没有那个程序员会有意使用释放的内存, 但是意外的使用却是常有的事。考虑下面释放单链表的正确代码:

        struct list *listp, *nextp;    for(listp = base; listp != NULL; listp = nextp) {        nextp = listp->next;        free(listp);    }

    请注意如果在循环表达式中没有使用临时变量 nextp, 而使用 listp =listp-> next会产生什么恶劣后果。# include <stdio.h>

    int find_substr(char* s1, char* s2);

    void main()
    {
        if(find_substr(”C is fun”, “is”) != -1)
            printf(”Substring is found.”);
    }

    /* 定义子函数 */
    int find_substr(char* s1, char* s2)
    {
        register int t;
        char *p, *p2;

        for(t=0; s1[t]; t++)
        {
            p = &s1[t];
            p2 = s2;

            while(*p2 && *p2==*p)
            {
                p++;
                p2++;
            }
            if(! *p2)
                return t;
        }
        return -1;
    }

    没有评论 »

    libevent

    十月 23, 2008 | c/c++, linux | RSS 2.0

    关于libevent, 先摘来网上某哥们一段描述:
     libevent是一个跨平台的事件驱动库,他目前支持Linux, *BSD,Mac OS X, Solaris 和 Windows。如果你将要开发的应用程序需要支持以上所列出的平台中的两个以上,那么强烈建议你采用这个库,即使你的应用程序只需要支持一个平台,选择libevent也是有好处的,因为它可以根据编译/运行环境切换底层的事件驱动机制,这既能充分发挥系统的性能,又增加了软件的可移植性。

    它封装并且隔离了事件驱动的底层机制,除了一般的文件描述符读写操作外,它还提供有读写超时、定时器和信号回调,另外,它还允许为事件设定不同的优先级,当前版本的libevent还提供dns和http协议的异步封装,这一切都让这个库尤其适合于事件驱动应用程序的开发。

    step1: 使用方法介绍
    OK,理论到位了,接下来简单介绍下libevent中几个核心函数:

    event_set(&c->event, sfd, event_flags, event_handler, (void *)c)
    把sfd这个文件描述符放入c->event,并且告知当事件event_flags发生时回调event_handler,并以c为回调参数。

    event_add(&ev, NULL)

    把ev注册到事件队列里面,第二个参数指定的是超时值,设定成NULL表示忽略这项设定。

    event_dispatch()
    表示进入事件循环,当队列里任何一个文件描述符发生事件的时候就会执行回调函数。

    step2: 演示例子
    下面举个小例子,就timer server吧,比较SB易懂咯,赶紧贴上源码:

     

    #include <netinet/in.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <stdio.h>
    #include <time.h>

    #include <event.h>


    void connection_time(int fd, short event, struct event *arg)…{
        char buf[32];
        struct tm t;
        time_t now;
        time(&now);
        localtime_r(&now, &t);
        asctime_r(&t, buf);
        write(fd, buf, strlen(buf));
        shutdown(fd, SHUT_RDWR);
        free(arg);
    }

    void connection_accept(int fd, short event, void *arg)…{

      /**//* for debugging */
        fprintf(stderr, ”%s(): fd = %d, event = %d. “, __func__, fd, event);
           /**//* 接受新的连接. */
           struct sockaddr_in s_in;
           socklen_t len = sizeof(s_in);    
           int ns = accept(fd, (struct sockaddr *) &s_in, &len);
        if (ns < 0) …{
            perror(”accept”);
            return;
        }
        
        struct event *ev = malloc(sizeof(struct event));
        event_set(ev, ns, EV_WRITE, (void *) connection_time, ev); 
        event_add(ev, NULL);
    }

    int main(void)…{
        /**//* 创建socket. */
        int s = socket(PF_INET, SOCK_STREAM, 0);
     if (s < 0) …{
            perror(”socket”);
            exit(1);
        }

        /**//* 绑定bind() */
        struct sockaddr_in s_in;
        bzero(&s_in, sizeof(s_in));
        s_in.sin_family = AF_INET;
        s_in.sin_port = htons(7000);
        s_in.sin_addr.s_addr = INADDR_ANY;
        if (bind(s, (struct sockaddr *) &s_in, sizeof(s_in)) < 0) …{
            perror(”bind”);
            exit(1);
        }
        /**//* 监听listen() */
     if (listen(s, 5) < 0) …{
            perror(”listen”);
            exit(1);
        }
        
        /**//* 初始化libevent. */
        event_init();
        
        /**//* 创建事件 */
        struct event ev;
        event_set(&ev, s, EV_READ | EV_PERSIST, connection_accept, &ev);
        /**//* 添加事件 */
        event_add(&ev, NULL);
        event_dispatch();
        return 0;

    怕有新手不懂,补充两句了:
    其实我一直认为,任何单一的编程模型本身,必能找出一个贴切的隐喻或现实参照物,只要洞悉本质,一切皆是那么容易(当然,我这里并不否认复合模型的复杂性)。
    libevent是基于事件的,看它名字都知道,我大致说说上面小例子的章法:
    拿个龌龊例子来隐喻下,假设有个买春店,这个店好比serversocket,各种服务(如按摩,做.爱)代表事件(连接,可读),小姐们代表一个个对等客户连接,客房代表空闲线程,嫖客好比过来的客户端连接,嫖客有欲望了,找上买春店,绑定到具体的对等连接(小姐),激发了连接事件并执行对应的回调(比如按摩,做.爱),此回调被安排在某个空闲的客房(比如线程池中某个空闲连接)里进行。如果嫖客与小妹是初次见面,那么留下联系方式,日后嫖客有需求,来店后可直接去找老相好(这就是为何在connection_accept回调函数中还要注册一次,因为第一次事件注册是没有目标性的,也即来个嫖客就随便找个小妹,而在回调中对连接事件的注册已经是有目标性的注册了,下次嫖客再来,就找上次的老相好,这样子做是有必要性的,对于嫖客,妹子而言可能旧情未了,需要重温,而对于网络通信,可能前面的通信并没有把数据传递完,所以需要把对等端连接关联起来,记录上次数据处理的状态信息,以备后续处理)。

     


         在《UNIX网络编程第1卷(第2版)》第6章 “I/O 复用:select和poll函数”(129页),有解释:
     
     ===============
     描述字符在什么条件下准备好?
     1、下列的4个条件中的任何一个满足时,socket准备好读(即有EV_READ):
      a)套接口接收缓冲区中的数据字节大于等于套接口接收缓冲区低潮限度的当前值(可以设定,默认为1)
      b)连接的读这一半关闭(即收到了FIN的TCP连接)。(也就是读到结尾了)
      c)套接口是一个接听套接口且已完成的连接数为非0。
      d)有一个套接口错误待处理。
     2、下列的3个条件中的任何一个满足时,socket准备好写(即有EV_WRITE):
      a)套接口发送缓冲区中的数据字节大于等于套接口发送缓冲区低潮限度的当前值(可以设定,默认为2048),且:
       aa)套接口已连接 或 bb)套接口不需要连接(如UDP)
      b)连接的写这一半关闭。
      c)有一个套接口错误待处理。
     ===============
     
       根据刚才的timeserver中的例程,当client有socket连上来,应该是EV_READ,因为满足1中的c“套接口是一个接听套接口且已完成的连接数为非0”,当该连接被acept以后,就有EV_WRITE了,因为它满足2中的a“套接口发送缓冲区中的数据字节大于等于套接口发送缓冲区低潮限度的当前值,且套接口已连接”。所以,event_set的时候,EV_READ是有人连上来,回调函数是connection_accept(),而EV_WRITE是在accept以后。
     
         那EV_PERSIST呢?
         EV_PERSIST,是在event发生了以后,不从队列中拿开,就是下次再有这个消息的时候,继续调那个回调函数,知道程序主动调用了event_del后,才从队列中删除--就算有那个消息也不回调函数。
         connection_accept函数,只要有连接上来,就要调用的,所以它需要设置为EV_PERSIST。

    没有评论 »