最近遇到的几个纯C编程的陷阱
首先是一个不容易看出来的语法上的陷阱



经过调试得出的错误是对非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编程的陷阱的更多相关文章
- UITableView自定义Cell中,纯代码编程动态获取高度
在UITableView获取高度的代理方法中,经常需要根据实际的模型重新计算每个Cell的高度.直接的做法是在该代理方法中,直接根据模型来返回行高:另 [1]-(CGFloat)tableView:( ...
- MyBatis学习(一)————纯jdbc编程
什么是JDBC JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java ...
- Android 网络编程的陷阱
陷阱一,不要在主线程或者UI线程中建立网络连接 Androd4.0以后,不允许在主线程中建立网络连接,不然会出现莫名其妙的程序退出情况.正确的做法是在主线程中,创建新的线程来运行网络连接程序. // ...
- Haskell 函数式编程快速入门【草】
什么是函数式编程 用常规编程语言中的函数指针.委托和Lambda表达式等概念来帮助理解(其实函数式编程就是Lambda演算延伸而来的编程范式). 函数式编程中函数可以被非常容易的定义和传递. Hask ...
- 【原创】高性能网络编程(二):上一个10年,著名的C10K并发连接问题
1.前言 对于高性能即时通讯技术(或者说互联网编程)比较关注的开发者,对C10K问题(即单机1万个并发连接问题)应该都有所了解."C10K"概念最早由Dan Kegel发布于其个人 ...
- 函数响应式编程(FRP)—基础概念篇
原文出处:http://ios.jobbole.com/86815/. 一函数响应式编程 说到函数响应式编程,就不得不提到函数式编程,他们俩有什么关系呢?今天我们就详细的解析一下他们的关系. 现在下面 ...
- Python 函数式编程 & Python中的高阶函数map reduce filter 和sorted
1. 函数式编程 1)概念 函数式编程是一种编程模型,他将计算机运算看做是数学中函数的计算,并且避免了状态以及变量的概念.wiki 我们知道,对象是面向对象的第一型,那么函数式编程也是一样,函数是函数 ...
- 从StackOverflow来的值得回味的编程观点
从StackOverflow来的值得回味的编程观点 很多有意思的话语 在 2012年06月08日 那天写的 已经有 4148 次阅读了 感谢 参考或原文 www.csdn.net 服务器君 ...
- 初次踏上GUI编程之路(有点意思,详细介绍了菜鸟的学习之路)
初次踏上GUI编程之路 —— 我的Qt学习方法及对Qt认识的不断转变 -> 开始接触GUI与开始接触Qt: 话说,我第一次看见“Qt”这一个名词,好像是在CSDN网站的主页上吧,因为CSDN好像 ...
随机推荐
- ie下的bug之button
场景描述: 现在页面设计是都喜欢自定义按钮样式,某日接收到页面发现在ie下有bug,上代码: <div> <button><span><a href=&quo ...
- yii2 实现excel导出功能
官方教程地址:http://www.yiiframework.com/extension/yii2-export2excel/ 安装: Either run php composer.phar req ...
- 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 ...
- python3与python2中的string.join()函数
在python2中,string 模块中有一个join()函数,用于以特定的分隔符分隔源变量中的字符串,将其作为新的元素加入到一个列表中,例如: body=string.join(( "Fr ...
- 构建Uber端到端技术栈的十条经验(转载)
好文章就得分享: 一.SOA 系统设计包括若干个层面.先说顶层的系统设计原则,如 REST.SOA.由于 Uber 之前一直算一个创业公司,所以开发速度至关重要,由于微服务能够极大地促进不同组件的平行 ...
- GPU编程自学2 —— CUDA环境配置
深度学习的兴起,使得多线程以及GPU编程逐渐成为算法工程师无法规避的问题.这里主要记录自己的GPU自学历程. 目录 <GPU编程自学1 -- 引言> <GPU编程自学2 -- CUD ...
- python3精简笔记(二)——函数
函数 下面的地址可以查看函数: https://docs.python.org/3/library/functions.html 也可以在交互式命令行通过help()查看函数的帮助信息. 如: > ...
- Java集合体系总结
一.集合框架 集合是容纳数据的容器,java常用的集合体系图如下.以集合中是否运行重复元素来分,主要有List和Set接口,List集合中可以有重复元素,Set集合集合中的元素不可重复,Iterato ...
- Okhttp之连接池ConnectionPool简单分析(一)
开篇声明:由于本篇博文用到的一些观点或者结论在之前的博文中都已经分析过,所以本篇博文直接拿来用,建议读此博文的Monkey们按照下面的顺序读一下博主以下博文,以便于对此篇博文的理解: <Okht ...
- JMter压力测试
一. 压力测试场景设置 一般我们在做压力测试的时候,分单场景和混合场景,单场景也就是咱们压测单个接口的时候,多场景也就是有业务流程的情况下,比如说一个购物流程,那么这样的场景就是混合场景,就是有多个接 ...