存档

文章标签 ‘C/C++’

写C程序时犯的超低级致命错误

三月 7th, {2010 4 条评论 4,400 人阅读过  

这几天一直在用C写程序,对于C/C++的内存管理方面的机制总是很小心,每次malloc之后都记得一定要free,可却犯了一个最初始的低级错误。

写了一个这样的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned char* strtohex(const char* in , int* len) 
{
	unsigned char* out = (unsigned char*)malloc(strlen(in)/2 );
	int i = 0 , j = 0 , k = 0 ,length = 0;
 	char tmp[2] = { 0 };
	memset(out , 0 , strlen(in) / 2);
	while(i < (int)strlen(in))
	{
		tmp[k++] = in[i++];
                tmp[k] = '\0';
		if(k == 2)
		{
			out[j++] = (unsigned char)strtol(tmp , (char**)NULL , 16);
			k = 0;
			length ++;
		}
	}
	if(len != NULL )
		*len = length;
	return out;
}

函数很简单,功能就是把一个16进制的字符串转换成unsigned char 数组,每两个字节转换成一个字节的unsigned char

strtol函数的功能是将一个字符串转换成一个长整型,存在第二个参数里面,然后返回转换结果,第三个参数是进制数,这里是16进制。

看上去没什么问题,但这个函数却给我带来了灾难性的后果,经常在free的时候会Segmentation fault ,仔细地检查各种malloc,各种长度都没有问题,可Segmentation fault 却时而出现时而不出现,当时就意识到自己犯了致命的错误,可却找不出在哪里,gdb也查不出问题所在。

后来有一次调用这个函数做转换的时候猛然间发现对同一个字符串有时候的转换结果会不一样,仔细地检查了一下这个函数终于发现了错误的根源:

	char tmp[2] = { 0 };

这里定义了一个2字节的字符数组,在存储的是一个2字节的16进制字符串,居然忘了里后的一个\0,需要3个字节才够,真是晕了,即使在malloc的时候也会记得多malloc一个字节存储\0,在这里却大意了,狠狠地警示一下自己。

分类: C/C++ 标签:

Automake生成Makefile过程简介

二月 26th, {2010 1 条评论 3,740 人阅读过  

有校友在byr上看了我发的openfetion,发信息问我关于automake的东西,其实我了解的也并不多,只是自己查了些资料,然后借鉴了一下别人的项目中相关文件的写法,于是就想照着openfetion中的相关文件把自己对它的理解简单写一下,算是整理一下学过的知识,以后也好有个参考。
Automake支持三种目录层次:flat,shadow,deep
1.flat 所有的源文件及相关文件都放在顶层目录中。
2.shadow 主要的源文件存放在顶层目录中,其它的存放在各个子目录中。
3.deep 所有的源文件都分别存放在各个子目录中。

我的程序用了deep模式,所有的源文件都放在了src目录中。下面写一下automake生成Makefile的步骤:
1.运行autoscan命令,生成configure.scan。

configure.scan就是configure.in的模板,对它做一些修改,然后改名为configure.in或者configure.ac就可以了(新版本的automake好像是configure.ac)。
configure.in:

AC_INIT(src/openfetion.cpp)
AM_INIT_AUTOMAKE(openfetion,0.1)
AM_CONFIG_HEADER(config.h)
AM_PATH_GTK_2_0(,,AC_MSG_ERROR(openfetion 0.1 needs GTK+ 2.0))
AM_PATH_XML2(,,AC_MSG_ERROR(openfetion 0.1 needs LIBXML2))
AC_PROG_CC
AC_PROG_CXX
AC_PROG_INSTALL
AC_OUTPUT(src/Makefile)
AC_INIT宏以任何一个源文件作为参数,它只是检查这个源文件的存在,也说意味着着源文件所在的目录存在
AM_INIT_AUTOMAKE 增加了几个标准的检查,它以程序名称和版本号作为参数
AC_PROG_CC 指出源代码可能是用C写的,如果源代码是用C++写的我们就需要AC_PROP_CXX
AC_PROG_INSTALL 会生成一个install目录文件,这样用户就可以通过输入“make install”来安装这个软件
AC_OUTPUT 指出将会生成的Makefile文件的名字
AC_CONFIG_HEADER 表示将会使用config.h文件,autoconf需要一个config.h.in文件,用它来生成config.h,config.h.in可以通过autoheader工具生成
AM_PATH_GTK_2_0(,,AC_MSG_ERROR(openfetion 0.1 needs GTK+ 2.0))
AM_PATH_XML2(,,AC_MSG_ERROR(openfetion 0.1 needs LIBXML2)) 这两句话检查系统中是否安装了程序所需要共享库, GTK+2.0和libxml2,这两个库在安装的时候分别安装了AM_PATH_GTK_2_0和 AM_PATH_XML2这两个宏,所以可以用这种方法检测,如果系统中没有安装libxml2,configure脚本就会执行失败,并报错:openfetion 0.1 needs LIBXML2
关于library的检查可参见文章:Using C/C++ libraries with Automake and Autoconf

其它的就不详细说了,需要的时候可以谷歌。
2.在顶层目录中创建一个Makefile.am,在其它各级需要的子目录中也创建Makefile.am。

我的顶层目录Makefile.am中只有下面几句话:

1
2
3
4
5
6
7
8
9
10
#子目录变量,用于递归处理各级子目录
SUBDIRS=src
#安装路径,可使用./configure --prefix=/usr/local/openfetion修改
prefix=/usr/local
#数据文件的安装路径
datadir=$(prefix)/skin
#数据文件的具体内容,这里的意思是要将skin目录下的所有文件安装到/usr/local/skin中
data_DATA=skin/* 
#程序打包时要加入的其它文件,使用make dist生成tar.gz文件时会放进去的东西
EXTRA_DIST=skin

src目录下的Makefile.am文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#libxml2 , GTK+2.0和GThread-2.0的cflags和libs
#libxml2安装了xml2-config脚本,GTK+2.0和GThread-2.0安装了.pc pkg-config文件
AM_CPPFLAGS=`xml2-config --cflags` `pkg-config --cflags gtk+-2.0 gthread-2.0`
AM_LDFLAGS=`xml2-config --libs` `pkg-config --libs gtk+-2.0 gthread-2.0`
#预定义的目录,prefix已经说过了
prefix=/usr/local
#这个是可执行文件安装的目录
bindir=$(prefix)/bin
#这个是自定义的目录,用来安装包含协议实现部分的静态库的
slibdir=/usr/lib
#编译生成可执行文件名称
bin_PROGRAMS=openfetion
#这里必须以上一步定义的名称为前缀,编译openfetion所需要的源文件
openfetion_SOURCES=openfetion.cpp  fetion_ui.cpp  ... login_ui.cpp
#编译openfetion所需要的库文件,libfx.a是我事先编译好了的。
openfetion_LDADD=libfx.a
#这个变量前面也说了,要把下面这些文件打包放到tar.gz中
EXTRA_DIST= 	   fetion_ui.h  main_ui.h  .... libfx.a
#要安装的头文件,执行完make install后,这些文件将被安装到/usr/include里面
include_HEADERS=   fgroup.h   flogin.h ... common.h
#自定义目录的数据文件,执行完make install后,这个文件就会被安装到/usr/lib里面
slib_DATA=libfx.a
3.生成GNU风格的项目时需要在顶层目录中你创建NEWS、 README、 ChangeLog 、AUTHOR这几个文件。
touch NEWS README ChangeLog AUTHOR
如果不需要生成GNU风格的项目就不需要创建这几个文件,而是需要在Makefile.am中加入
AUTOMAKE_OPTIONS = foreign
这是执行automake命令时的选项
4.执行aclocal。
由configure.in生成aclocal.m4
5.执行autoconf
由configure.in和aclocal.m4生成configure脚本
6.执行automake
由Makefile.am和configure.in生成各级目录下的Makefile.in
./configure执行的时候会扫描各个目录下的Makefile.in生成不同的Makefile。然后就可以执行make和make install了

GNU Automake工具集的功能远不止这些,我了解地也不够深刻,继续学习。

分类: Linux 标签: , , ,

linux下获取当前程序的绝对路径

二月 25th, {2010 没有评论 4,818 人阅读过  

在linux下运行的程序经常需要获取自己的绝对路径,程序可能需要引用外部的资源文件,比如在../skin/目录下的图片,这样普通程序是没有问题,但当程序在安装到/usr/bin/目录中,或者为程序建立连接以后就会出现问题,我们可以直接通过运行程序的链接来运行程序,这样../skin/目录就找不到了,因为当前目录并不是程序所在的目录,而且链接所在的目录,所以在它的上一级目录中根本找不到skin目录,所以就需要获取程序的绝对路径,用到了一个函数readlink,原型如下:

1
2
3
#include <unistd.h>
 
ssize_t readlink(const char *restrict path , char *restrict buf , size_t bufsize);

该函数的作用是读取符号链接的原路径,将它存到buf中,返回添充到buf中的字节数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <unistd.h>
#include <stdio.h>
 
int main(int argc , char* argv[])
{
	char buf[1024] = { 0 };
	int n;
 
	n = readlink("/bin/mail" , buf , sizeof(buf));
	if( n > 0 && n < sizeof(buf))
	{
		printf("%s\n" , buf);
	}
}

程序运行后输出:/usr/bin/mailx

linux系统中有个符号链接:/proc/self/exe 它代表当前程序,所以可以用readlink读取它的源路径就可以获取当前程序的绝对路径,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <unistd.h>
#include <stdio.h>
 
int main(int argc , char* argv[])
{
	char buf[1024] = { 0 };
	int n;
 
	n = readlink("/proc/self/exe" , buf , sizeof(buf));
	if( n > 0 && n < sizeof(buf))
	{
		printf("%s\n" , buf);
	}
}
1
2
ouclwp@darkstar:~/socket$ ./read 
/home/ouclwp/socket/read

分类: C/C++ 标签: ,

关于C/C++的内存管理和函数回调

一月 29th, {2010 5 条评论 3,556 人阅读过  

最近一直都在用C++写程序,这几年都在用像C#,java这样的高级语言,很多C/C++的机制用起来突然发现自己已经那么不熟练了,甚至有些东西在当初就没有理解透彻,简单地说一下自己遇到的一些小儿科的问题。

1.C的内存管理

首先便是C/C++的内存管理机制,C#和java这样的高级语言是很牛的,他们有自己的自动内存管理机制,不需要程序员去人为地释放分配的内存,而之于C++便不同了,每一次在内存中new一块内存空间,在使用完后都要及时地释放,一个new便一定要对应一个delete,new完没有delete,这可能不会马上出现太大的问题,不过程序却会蚕食着内存,直到耗尽。

另外就是变量在内存中的存储位置问题,其实以前在用高级语言的时候都从来不考虑这个问题的,而现在不同了,想做一个c/c++ coder ,就需要把这些细节都弄清楚,通过new动态分配的内存空间是存在于堆上的,而且函数内部定义的变量的内存空间是存在于堆栈上的,当初对这个问题产生了一个弱弱的疑问,像下面这段代码:

1
2
3
4
void test()
{
       char* name = new char[10];
}

当初就是想不明白,那name到底是存放在哪里的呢,哈,其实现在想想都觉得当初真是傻啊,name是一个指针,它本身就是一个占四个字节空间的变量,这4个字节理所当然是存在于栈上的了,而name所指向的那10个字节的内存空间是存在于堆上的,逻辑关系其实非常清晰。

因为函数成员变量只在函数局部有效,函数执行完毕后堆栈上的东西便不存在了,于是想要返回一个字符串的时候就有可能会出这样的错误:

1
2
3
4
5
char* test()
{
       char name[] = "hello world";
       return name;
}

这样写的话编译器好像会提示吧,记不清了,反正我没这样写过,返回字符串我常用的方法有两种:

1
2
3
4
5
6
char* test()
{
       char* name = new char[10];
       ........
       return name;
}

这样在通过调用test函数得到一个返回值,用完后必须要记得释放那块内存空间,这个问题很容易遗忘,检查代码的时候发现了好几外这样的问题。
另外一种是通过函数参数

1
2
3
4
void test(char* name)
{
        strcpy(name , "hello world!");
}

这样在使用的时候就可以这样调用了:

1
2
char name[20] = { 0 };//我习惯这样初始化字符数组
test(name);

或者new一个字符数组,这样在用完后还是在这一层次上delete掉,一个new对应一个delete,逻辑关系很明确,不太容易出问题。

2.C++使用回调函数

这个问题以前也没有考虑过,最近在用C++和GTK+做开发,本来是打算用QT,后来觉得在GTK的桌面环境下QT的程序跑起来看上去总是很别扭,后来也考虑了gtkmm,可觉得gtkmm不能用在c里面,我也不一定一直都用C++,不如借此机会学习学习GTK,然后就选了GTK,还是习惯面向对象,然后最后决定用C++和GTK这种组合,自己都觉得有点不伦不类。

GTK中的信号机制是采用的函数回调,这在C里面是肯定没有问题的,但如果要在C++的类中引入回调函数,确实有一些麻烦,C++是不允许将非静态成员函数作为回调函数的,这也就是说回调函数一定要是一个类的静态成员函数,当然这样又出现了一个问题,非静态成员不能够调用类的成员变量 和成员函数,这是个大问题,然后我就想了一种方法,下面是一个gtk中信号实现的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class test
{
    public:
       test();
       void hello();
       void initialize();
       static test* pthis;
       static void testfunc(GtkWidget* widget , gpointer data);
};
void test::initialize()
{
    test* pthis = this;//静态成员函数是不能在类中赋值的,这里相当于定义。
    ...............
    g_signal_connect(someobj , "clicked" , G_CALLBACK(testfunc) , pthis);
}
void test::testfunc(GtkWidget* widget , gpointer data);
{
    test* pthis = static_cast<test*>(data);
    pthis->hello();
}

我一直都是这样干的,不知道这算不算个野路子,不过我的程序是没有在这方面出过问题。

分类: C/C++ 标签: ,