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

经过调试得出的错误是对非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. Pycharm更换主题

    更换主题(jar包) 1. 下载皮肤主题(jar) 去 http://www.themesmap.com/ 选择自己喜欢的主题下载 2. 导入皮肤主题 导入方法:file–>Import Set ...

  2. $.ajaxComplete()

    https://www.cnblogs.com/RachelChen/p/5433881.html 全局ajax事件   必须当页面上存在任何ajax请求的时候都将触发这些特定的全局ajax处理函数. ...

  3. 由浅入深了解EventBus:(二)

    概念 深入学习EventBus框架,就必须理解EventBus的相关原理和一些概念: Subscribe 在EventBus框架中,消息的处理接收方法必须要“@Subscribe”注解来进行标注: p ...

  4. C++/C extern关键字详解 EntryPointNotFoundException处理

    最近在弄C#帮公司做一个图像识别的功能,用到了第三方的dll,在调用dll过程中就出现了一个问题.EntryPointNotFoundException异常.遇到这种异常,很大可能就是在生成dll时函 ...

  5. iOS系统知识架构(转)

    转载的,哪些所谓的资深开发,谁敢说自己没有知识盲区?http://ios.skyfox.org/route.html

  6. 前端神器!!gulp livereload实现浏览器自动刷新

    首先gulp是基于Node的,所以确保你已经安装 node.js,在Nodejs官方网站下载跟自己操作系统相对应的安装包. 先说一下gulp安装流程: 1:全局安装gulp,操作为: npm inst ...

  7. Git详解之九 Git内部原理

    以下内容转载自:http://www.open-open.com/lib/view/open1328070620202.html Git 内部原理 不管你是从前面的章节直接跳到了本章,还是读完了其余各 ...

  8. erlang游戏开发tcp

    之前在开发游戏的时候我们采用smartfoxserver这个java开发的游戏引擎,这个引擎在开发回合制游戏方面速度还是不错的.但是面对客户日益增长的需求还是有些力不从心.比如集群,比如灾备,热切换, ...

  9. 使用HttpURLConnection请求multipart/form-data类型的form提交

    写一个小程序,模拟Http POST请求来从网站中获取数据.使用Jsoup(http://jsoup.org/)来解析HTML. Jsoup封装了HttpConnection的功能,可以向服务器提交请 ...

  10. iOS常识名词解释 2016/04/05

    Bundle : http://www.cnblogs.com/BigPolarBear/archive/2012/03/28/2421802.html http://blog.sina.com.cn ...