首先是一个不容易看出来的语法上的陷阱

经过调试得出的错误是对非socket的socket操作出错,sockfd在调试过程中发现是0,不是一个合理的文件描述符。

仔细一看原来是括号忘记加了,该运算是先用socket返回一个文件描述符fd,然后将fd<0的结果赋给sockfd,正常创建的socket的文件描述符为正,所以fd<0的结果为0,C语言中0代表false,任何赋值运算都是true,所以也经常会出现写掉一个=导致条件表达式一直为true。比如if (ptr = NULL)。所以很多人推荐将右值写在等号左边来让编译器来检查这种低级错误,比如if (NULL == ptr)。

这种写法是《Unix网络编程》上推荐的写法,但是由于该书时间比较久远,那时候用的还是C89标准,也就是所有变量必须先定义再使用。以前看过的很多书上VC6.0的MFC程序代码就是这样,循环变量也在函数开头定义int i, j;(不过VC6本身对for循环就是不符合标准的= =b)

但是C99标准已经不必将所有变量在函数最开始就定义了,这种惯用法不一定值得大力提倡,毕竟括号多了容易看漏。

修改后

int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket error");
return 1;
}

清晰易懂。不过如果sockfd早就定义了,或者作为类的成员变量,之前那种2行合并的写法更好。

下一个问题,字符指针、字符数组的问题

void printPermisson(struct stat* buf)
{
char* str = "-rwxrwxrwx\n";
int flag[9] = { S_IRUSR, S_IWUSR, S_IXUSR,
S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH };
if (S_ISDIR(buf->st_mode))
str[0] = 'd';
for (int i = 0; i < 9; i++) {
if (!(buf->st_mode & flag[i]))
str[i + 1] = '-';
}
write(STDOUT_FILENO, str, 11);
}

首先是这么写出错,因为虽然是char*,但是这种字面型字符串(string literal)是分配在静态区的(注意,"-rwxrwxrwx\n"是在静态常量区,但是指针str是在栈上),这里的char*和const char*无异,不能修改。

OK,于是改成了指针数组char str[12]

当然,这里我最初是写成返回字符串char*的,printf的工作留给函数使用者,即

char str[12] = "-rwxrwxrwx\n";
// ... 处理
return str;

这是很严重的错误,指针str和它指向的字符数组内容"-rwxrwxrwx\n"都是在栈上,也就是说出了作用域后就会释放内存,返回的指针指向一片不可用的内存!

所以这里只有char* str = (char*)malloc(12);这么动态分配内存,字符数组内容是在堆上,需要用户手动释放。

然而,并不是“只有”这种手段,见我这篇博客最后的错误更正Linux和Windows的遍历目录下所有文件的方法对比

除了动态分配外,也可以把字符串内容分配到静态区(不是之前string literal分配的静态常量区),它的声明周期和全局变量一样,而不是出了作用域就销毁,记得以前刚上C语言课时考试必考的一题就是这种,求func()函数运行几次后的输出

void func()
{
int a = 0;
static int b = 0;
auto int c = 0;
a++;
b++;
c++;
printf("a=%d,b=%d,c=%d\n", a, b, c);
}

注意,这里的auto是C标准的关键词,一个毫无卵用(仅仅是为了和static区分)的关键字,因为变量默认就是auto的,auto变量生存周期是作用域内(即{}内部

),而static变量则是一开始就存在,直到程序结束。

PS:C++11开始auto已经变成非常好用也非常容易滥用的语法糖了,自动类型推导。跟这个auto完全不一样。

回正题,于是呢,利用static变量生存周期的特性,把之前返回字符串char*的函数写成这样就没问题了

static char str[12] = "-rwxrwxrwx\n";
// ... 处理
return str;

而readdir函数(见我之前提到的博客)返回的struct dirent*则是类似这样的手段来避免用户来释放内存(可惜也有像我这种自作聪明的人)

最后就记录刚才调试了很久的错误,感觉C真是写得心累

程序是读取Linux下的空洞文件(lseek或truncate等函数造成的,包含大量空白区域,即字符为\0,如果用普通的char buf[BUFFSIZE]来存储,很有可能中途就因为strlen(buf)为0导致退出,于是我的思路就是用lstat计算出文件大小size,用size / BUFFSIZE + 1作为循环次数。

但是却输出了这样的东西。

原因是我没有给buf初始化为0,然后每次读写的都是sizeof(buf)-1,为末尾留个结束符\0,但实际上字符数组未初始化,最后一位的字符是随机产生的(不为0)。

    char buf[LEN];
printf("file size: %ld\n", statbuf.st_size);
for (int i = 0; i < statbuf.st_size / LEN; i++) {
if (read(fd, buf, sizeof(buf) - 1) < 0)
err_sys("read error");
// ...
}

纠正方法就是char buf[LEN] = { 0 };

唉,暂时说这么多了,基本功还是很不扎实,当然也有C++写习惯了的原因(构造/析构自动完成),实际写代码出现这些问题却不能很快速地发现。

最近遇到的几个纯C编程的陷阱的更多相关文章

  1. UITableView自定义Cell中,纯代码编程动态获取高度

    在UITableView获取高度的代理方法中,经常需要根据实际的模型重新计算每个Cell的高度.直接的做法是在该代理方法中,直接根据模型来返回行高:另 [1]-(CGFloat)tableView:( ...

  2. MyBatis学习(一)————纯jdbc编程

    什么是JDBC  JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java ...

  3. Android 网络编程的陷阱

    陷阱一,不要在主线程或者UI线程中建立网络连接 Androd4.0以后,不允许在主线程中建立网络连接,不然会出现莫名其妙的程序退出情况.正确的做法是在主线程中,创建新的线程来运行网络连接程序. // ...

  4. Haskell 函数式编程快速入门【草】

    什么是函数式编程 用常规编程语言中的函数指针.委托和Lambda表达式等概念来帮助理解(其实函数式编程就是Lambda演算延伸而来的编程范式). 函数式编程中函数可以被非常容易的定义和传递. Hask ...

  5. 【原创】高性能网络编程(二):上一个10年,著名的C10K并发连接问题

    1.前言 对于高性能即时通讯技术(或者说互联网编程)比较关注的开发者,对C10K问题(即单机1万个并发连接问题)应该都有所了解."C10K"概念最早由Dan Kegel发布于其个人 ...

  6. 函数响应式编程(FRP)—基础概念篇

    原文出处:http://ios.jobbole.com/86815/. 一函数响应式编程 说到函数响应式编程,就不得不提到函数式编程,他们俩有什么关系呢?今天我们就详细的解析一下他们的关系. 现在下面 ...

  7. Python 函数式编程 & Python中的高阶函数map reduce filter 和sorted

    1. 函数式编程 1)概念 函数式编程是一种编程模型,他将计算机运算看做是数学中函数的计算,并且避免了状态以及变量的概念.wiki 我们知道,对象是面向对象的第一型,那么函数式编程也是一样,函数是函数 ...

  8. 从StackOverflow来的值得回味的编程观点

    从StackOverflow来的值得回味的编程观点 很多有意思的话语 在 2012年06月08日 那天写的     已经有 4148 次阅读了 感谢 参考或原文 www.csdn.net   服务器君 ...

  9. 初次踏上GUI编程之路(有点意思,详细介绍了菜鸟的学习之路)

    初次踏上GUI编程之路 —— 我的Qt学习方法及对Qt认识的不断转变 -> 开始接触GUI与开始接触Qt: 话说,我第一次看见“Qt”这一个名词,好像是在CSDN网站的主页上吧,因为CSDN好像 ...

随机推荐

  1. Truncate a string

    用瑞兹来截断对面的退路! 截断一个字符串! 如果字符串的长度比指定的参数num长,则把多余的部分用...来表示. 切记,插入到字符串尾部的三个点号也会计入字符串的长度. 但是,如果指定的参数num小于 ...

  2. jmap和jstack使用

    http://blog.csdn.net/sinat_29581293/article/details/70214436

  3. taskset -pc PID 查看线程占用cpu核

    taskset -pc  PID 可以用于 查看 当前线程 对应绑定的 在 哪个核上面. 这个 可以用于 程序优化, 查看 哪个线程占用的 cpu 比重比较高 首先 可以通过  top  -H   - ...

  4. 弄懂flex布局

    目前在不考虑IE以及低端安卓机(4.3-)的兼容下,已经可以放心使用flex进行布局了.什么是flex布局以及它的好处,这里就不再赘述. 在这篇文章里,想说说flex布局的属性语法及其细节.那么网上也 ...

  5. ZOJ-2972-Hurdles of 110m(线性dp)

    Hurdles of 110m Time Limit: 2 Seconds      Memory Limit: 65536 KB In the year 2008, the 29th Olympic ...

  6. 【转】netlink socket编程实例

    [转]netlink socket编程实例 转自:http://blog.chinaunix.net/uid-14753126-id-2983915.html 关于Netlink IPC方式的介绍,请 ...

  7. shell getopts学习

    #!/bin/bash while getopts i:vh name do case $name in i) opt=1 echo $OPTARG;; v) opt=2 echo 2;; h) op ...

  8. 【Spring实战】Spring容器初始化完成后执行初始化数据方法

    一.背景知识及需求 在做WEB项目时,经常在项目第一次启动时利用WEB容器的监听.Servlet加载初始化等切入点为数据库准备数据,这些初始化数据是系统开始运行前必须的数据,例如权限组.系统选项.默认 ...

  9. HAWQ + MADlib 玩转数据挖掘之(二)——矩阵

    矩阵是Madlib中数据的基本格式,通常是二维的.在Madlib中,数组的概念与向量类似,数组通常是一维的,是矩阵的一种特殊形式. 一.矩阵表示 MADlib为矩阵提供了两种表示形式:稠密和稀疏. 1 ...

  10. 用前序和中序重建二叉树 python

    程序实现了用二叉树的前序遍历序列和中序遍历序列重建二叉树,代码用python实现. 首先定义二叉树节点的类: class TreeNode: def __init__(self, x): self.v ...