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

经过调试得出的错误是对非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. 在 Bash on Ubuntu 上安装Nginx

    前言 Win10 上的 Bash on Ubuntu 是个很好用的玩具,让windows开发环境下的人能无缝操练Linux,但是涉及到网络部分还是有很多要该进的地方,比如Nginx的安装就遇到了问题. ...

  2. hdu4685

    题解: 二分图匹配 对于每一个单身狗 见一个虚拟的人 然后就可以做了 代码: #include<cstdio> #include<cstring> #include<al ...

  3. LeetCode OJ:Search in Rotated Sorted Array(翻转排序数组的查找)

    Suppose a sorted array is rotated at some pivot unknown to you beforehand. (i.e., 0 1 2 4 5 6 7 migh ...

  4. 《Effective C++》第2章 构造/析构/赋值运算(1)-读书笔记

    章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effecti ...

  5. pgpool安装配置整理

    安装PostgreSQL并配置三节点流复制环境,就不仔细说了,大致步骤如下: 1.下载源码 2.解压安装,如果在./configure --prefix=/usr/pgsql-10执行时提示要--wi ...

  6. jfreechart在jsp中画图方式

    这个问题一直困扰我好久,今天算是稍微找到一点解决思路了,在网上搜了好多列子,大部分的都是用servlet来实现画图,偶然找到一个列子用的是org.jfree.chart.servlet.Servlet ...

  7. TreeMap实现原理及源码分析

    TreeMap是一个有序的key-value集合,基于红黑树(Red-Black tree)实现.该映射根据其键的自然顺序进行排序,或者根据创建时提供的Comparator进行排序. 对于TreeMa ...

  8. React-Native进阶_2.加载指示动画 ActivityIndicator

    在安卓原始 App中使用的加载框 ProgressBar 在React -Native 中也是有相对应的视图,叫做ActivityIndicator,对应ios 中React-Native 提供的是  ...

  9. HDU 2268

    http://acm.hdu.edu.cn/showproblem.php?pid=2268 小学四年级应用题,让我找回了儿时的快乐... #include <iostream> #inc ...

  10. js之简易计算器

    <!DOCTYPE html PUBLIC "-//W3C//Dli XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...