存档

‘C/C++’ 分类的存档

linux0.11里面发现的好玩的链表指针迭代方法

八月 30th, {2010 2 条评论 2,768 人阅读过  

linux的内存管理一直是让我最困惑的,2.6版本内核里面各种复杂的机制搞得我云里雾里,甚至搞不明白内存管理最基本的原理是什么,于是翻阅早期版本的内核0.11,最底层的内存分段和分页我都已经在我的plinux上实践过了,读到malloc的时候发现了linus用了一种很好玩的指针迭代方法,至少我以前没见过,见识短浅,大家莫笑。

linus把一页内存分成一个个具有相同大小的内存块,然后又将这些个内存块组成一个链表,而这些内存块都是将来要分配出来的空闲内存,它们内部并不包含有任何对象,要组成链表就必然要求有指针,linus把每块内存的前4个字节拿出来作为指针,指向下一块内存的首地址:

char  *cp;
/* 在这里获取一页空闲内存 */
(void*)cp = get_free_page();
 
for(i = PAGE_SIZE/bdir->size; i > 1; i --){
/* 我觉得这里很有意思,把一个字符指针强制转换成指向字符指针的指针,
然后再对其解引用赋值,相当于把cp开始的前4个字节作为一个指针并对它进行定向 */
    *((char**)cp) = cp + bdir->size;
    cp += bdir->size;
}

然后在指针迭代的时候用了下面这种方法,不复杂,只是觉得挺有意思的:

void *retval = (void*)freeptr;
freeptr = *((void**)retval);

这样freeptr就指向了下一块空闲内存。

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

C语言获取汉字拼音首字母

七月 14th, {2010 6 条评论 4,243 人阅读过  

很多网友提意见让我给小飞信加上好友搜索功能,这几天太忙,抽时间把其它的功能加了一下,现在就剩下这个功能没做好了,想做成按汉字首字母搜索的那种,于是查了查资料写了个把汉字转换成首字母的程序,贴一下有需要的可以拿去用,也顺便给自己做个备份。

下面是程序,用了glib里面的一个函数g_convert,用iconv也可以,g_convert更方便一些就直接拿来用了。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>
 
gchar getpychar(guchar uword0 , guchar uword1)
{
	gchar pychar;
 
	int i1 = (short)(uword0 - '\0');
	int i2 = (short)(uword1 - '\0');
 
	int tmp = i1 * 256 + i2;
 
	if(tmp >= 45217 && tmp <= 45252) pychar = 'A'; 
	else if(tmp >= 45253 && tmp <= 45760) pychar = 'B'; 
	else if(tmp >= 45761 && tmp <= 46317) pychar = 'C'; 
	else if(tmp >= 46318 && tmp <= 46825) pychar = 'D'; 
	else if(tmp >= 46826 && tmp <= 47009) pychar = 'E'; 
	else if(tmp >= 47010 && tmp <= 47296) pychar = 'F'; 
	else if(tmp >= 47297 && tmp <= 47613) pychar = 'G'; 
	else if(tmp >= 47614 && tmp <= 48118) pychar = 'H'; 
	else if(tmp >= 48119 && tmp <= 49061) pychar = 'J'; 
	else if(tmp >= 49062 && tmp <= 49323) pychar = 'K'; 
	else if(tmp >= 49324 && tmp <= 49895) pychar = 'L'; 
	else if(tmp >= 49896 && tmp <= 50370) pychar = 'M'; 
	else if(tmp >= 50371 && tmp <= 50613) pychar = 'N'; 
	else if(tmp >= 50614 && tmp <= 50621) pychar = 'O'; 
	else if(tmp >= 50622 && tmp <= 50905) pychar = 'P'; 
	else if(tmp >= 50906 && tmp <= 51386) pychar = 'Q'; 
	else if(tmp >= 51387 && tmp <= 51445) pychar = 'R'; 
	else if(tmp >= 51446 && tmp <= 52217) pychar = 'S'; 
	else if(tmp >= 52218 && tmp <= 52697) pychar = 'T'; 
	else if(tmp >= 52698 && tmp <= 52979) pychar = 'W'; 
	else if(tmp >= 52980 && tmp <= 53640) pychar = 'X'; 
	else if(tmp >= 53689 && tmp <= 54480) pychar = 'Y'; 
	else if(tmp >= 54481 && tmp <= 55289) pychar = 'Z'; 
	else pychar = ' ';
 
	return pychar;
}
 
gchar *getpystring(const gchar *in)
{
	gsize inlen , olen , i , j = 0;
	gchar *gword = g_convert(in , strlen(in)
			, "gb2312" , "utf8" , &inlen , &olen , NULL);
 
	guchar *uword = (guchar*)gword;
	gchar *out = (gchar*)malloc(olen);
 
	memset(out , 0 , olen);
 
	for(i = 0 ; i < olen ; i++){
		if(uword[i] >= 0xa1){
			if(uword[i] != 0xa3){
				out[j++] = getpychar(uword[i] , uword[i + 1]);
				i ++;
			}
		}else{
			out[j++] = (gchar)uword[i];
		}
	}
 
	return out;
 
}
 
int main(int argc , char **argv)
{
	printf("%s\n" , getpystring("linux是一个出色的操作系统"));
	return 0;
 
}

程序输出:linuxSYGCSDCZXT

分类: C/C++ 标签:

gstreamer播放wav文件

五月 6th, {2010 3 条评论 3,783 人阅读过  

有网友留言希望我在飞信上加上声音提示,下午的时候整了整,很早以前写过一个直接操作/dev/dsp的小程序用来播放wav,后来发现在当另一个程序在使用这个设备文件的时候,程序就无法打开/dev/dsp这个文件,也就无法完成播放,但因为实现简单我在飞信的第一个版本里面用了这种方法,后来觉得不好就直接把声音提示给去掉了。

在网上查了一下实现wav播放的方法,大多数都是这种直接操作/dev/dsp的,GTK也没有像QT那样的直接播放音频文件的类,于是我只好使用第三方的库了,网上很流行的gstreamer,今天简单读了一下它的手册,发现gstreamer确实是很强大,和GTK一样,它也是基于面向对象的思想来实现的,C语言的面向对象,这样对象和对象之间的继承和派生的关系他们都实现的很完美,有时间一定要仔细研究一下具体的实现方法,gstreamer还有一个更强大的特点是它的管道流机制,gstreamer里面的基本元素是element,element上有pad,每一个element都是一个独立的组件,来完成相应的功能,pad应该可以理解成接口吧,我也不知道该翻译成什么,element之间通过各种pad连接起来形成一个pipe,数据流就在这个pipe里面流动,每经过一个element就会经过一层过滤,比如经过数据压缩的element,数据解码的element,这让我想到了很多年前开发OA的时候里面的工作流,不过这种机制用在这里确实是个好主意,要实现一个复杂的功能只需要把各种独立实现某个小功能的element连接成一个pipe,数据流依次流过这个pipe的时候就完成了对这个数据流的复杂的处理,可扩展性也很高,当需要新的功能的时候只需要开发新的组件,提供相应的接口就可以添加到应用程序中去。

gst-inspect命令列出当前系统中已经安装了的gstreamer组件以及这些组件所具有的特性。

gst-launch命令创建并运行一个pipe,组件之间的管道用”!”作为分隔符,为了跟shell的管道分隔符区分开没有用”|”,下面是几个例子:

播放一个mp3文件,第二个组件mad是用来对mp3进行解码的,我系统上没装这个组件…
gst-launch filesrc location=music.mp3 ! mad ! osssink

其中location是组件filesrc的一个属性,filesrc是要从一个文件读取源数据,location指出了文件的路径
具体还有很多,详见http://linux.about.com/library/cmd/blcmdl1_gst-launch.htm

播放wav的时候需要用到wavparse这个组件,它在gst-plugins-good这个包里面,我的slackware没有预装,我在slackbuild.org上下载上来装上了。

在命令行用gst-launch命令播放的时候跟播放mp3时候类似,但在程序里面实现的时候就不同了,简单的把filesrc,wavparse和alsasink这三个组件连接起来会失败,这困扰了我好久,最后终于弄明白了,wavparse这个组件的sink pad只能在数据流来了以后才可以连接,因为wavparse需要先知道输入给它的数据的类型,所以在wavparse创建的时候link就会失败。解决的办法是使用信号动态地连接pad,gst_bin_add_many这个函数调用的时候会触发add_pad信号,回调函数参数列表里面就有新创建的pad,把这个pad连接到alsasink的sink pad上去就可以了,下面是gstreamer播放wav的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <gst/gst.h>
 
static void
add_pad (GstElement *element , GstPad *pad , gpointer data){
 
	gchar *name;
	GstElement *sink = (GstElement*)data;
 
	name = gst_pad_get_name(pad);
	gst_element_link_pads(element , name , sink , "sink");
	g_free(name);
}
 
static gboolean
bus_watch(GstBus *bus , GstMessage *msg , gpointer data)
{
    GMainLoop *loop = (GMainLoop *) data;
    if(GST_MESSAGE_TYPE(msg) == GST_MESSAGE_EOS){
        g_main_loop_quit(loop);
    }
    return TRUE;
}
 
void
play_file(const char *filename){
 
	GMainLoop *loop;
	GstElement *pipeline;
	GstBus *bus;
	GstElement *source , *parser , *sink;
 
	loop = g_main_loop_new(NULL , TRUE);
 
	pipeline = gst_pipeline_new("audio-player");
 
	source = gst_element_factory_make("filesrc" , "source");
	parser = gst_element_factory_make("wavparse" , "parser");
	sink = gst_element_factory_make("alsasink" , "output");
 
	g_object_set(G_OBJECT(source) , "location"
			, filename , NULL);
 
	bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
	gst_bus_add_watch(bus , bus_watch , loop);
	g_object_unref(bus);
 
	gst_bin_add_many(GST_BIN(pipeline)
			, source , parser , sink , NULL);
 
	g_signal_connect(parser
			, "pad-added" , G_CALLBACK(add_pad) , sink);
 
	if(! gst_element_link(source , parser)){
		g_warning("linke source to parser failed");
	}
 
	gst_element_set_state(pipeline , GST_STATE_PLAYING);
	printf("Start playing...\n");
	g_main_loop_run(loop);
	printf("Playing stopped!!!\n");
	gst_element_set_state(pipeline , GST_STATE_NULL);
	g_object_unref(pipeline);
}
 
int
main(int argc , char *argv[]){
 
	gst_init(&argc , &argv);
 
	play_file(argv[1]);
 
	return 0;
}

编译:

1
$gcc -o gst gst.c `pkg-config --cflags --libs gstreamer-0.10`

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

STUN协议的C程序

四月 26th, {2010 2 条评论 4,127 人阅读过  

写了一个小程序测试STUN协议,只可惜国内没有可用的STUN Server,UDP协议又不能穿透HTTP代理,所以我在教育网内没法测试更多功能了,只能写这么多,发出去的包wireshark显示为STUN,但由于测试用Server是国外的服务器,不加代理没法访问,所以就没办法了…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
 
#define STUN_SERVER_IP "132.177.123.13"
#define STUN_SERVER_PORT 3478
 
int socketfd;
 
struct stun_header{
	unsigned short type;
	unsigned short length;
	unsigned int transId1;
	unsigned int transId2;
	unsigned int transId3;
	unsigned int transId4;
};
 
struct mapped_address{
	unsigned padding : 8;
	unsigned family : 8;
	unsigned short port;
	unsigned int address;
};
 
int 
init_socket(){
	socketfd = socket(AF_INET , SOCK_DGRAM , 0);
}
 
int 
stun_rand(){
	srand(time(NULL));
	return rand();
}
 
struct stun_header* 
build_stun_header(unsigned short type){
 
	struct stun_header *header ;
 
	header = (struct stun_header*)malloc(sizeof(struct stun_header));
	header->type = htons(type);
	header->length = 0;
	header->transId1 = stun_rand();
	header->transId2 = stun_rand();
	header->transId3 = stun_rand();
	header->transId4 = stun_rand();
 
	return header;
}
 
int 
udp_send(void *data , int len
		, const char *ip , int port){
 
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = inet_addr(ip);
	addr.sin_port = htons(port);
 
	return sendto(socketfd , data , len , 0
			, (struct sockaddr*)&addr , sizeof(struct sockaddr));
 
}
 
int 
main(int argc , char *argv[]){
 
	init_socket();
	struct stun_header recv_header;
	struct sockaddr_in addr;
	size_t length;
 
	struct stun_header* header
		= build_stun_header(0x0001);
 
	udp_send(header , sizeof(struct stun_header)
			, STUN_SERVER_IP , STUN_SERVER_PORT);
 
	length = sizeof(struct sockaddr);
	recvfrom(socketfd , (void*)&recv_header , sizeof(recv_header) , 0
			, (struct sockaddr*)&addr , &length);
 
	return 0;
}

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

HTTP代理原理及Base64编码

四月 14th, {2010 2 条评论 5,480 人阅读过  

有网友留言希望我在飞信的新版本中加上代理功能,这两天折腾了一下,把HTTP代理给加上了,SOCKS代理太麻烦了,暂时应该不会考虑加它了,而且用的人也不多,加不加意义不大了。

写一下HTTP代理的原理,其实原理很简单,就是通过HTTP协议与代理服务器建立连接,协议信令中包含要连接到的远程主机的IP和端口号,如果有需要身份验证的话还需要加上授权信息,服务器收到信令后首先进行身份验证,通过后便与远程主机建立连接,连接成功之后会返回给客户端200,表示验证通过,就这么简单,下面是具体的信令格式:

CONNECT 59.64.128.198:21 HTTP/1.1
Host: 59.64.128.198:21
Proxy-Authorization: Basic bGV2I1TU5OTIz
User-Agent: OpenFetion

其中Proxy-Authorization是身份验证信息,Basic后面的字符串是用户名和密码组合后进行base64编码的结果,也就是对username:password进行base64编码。

其实编码对安全性没什么意义,base64严格意义上都已经不能算是加密了,现在信息安全这么受重视的年代,不需要密钥的加密算法还是叫编码更贴切一些,抓到这种包之后瞬间就可以得到用户名和密码。

HTTP/1.0 200 Connection established

OK,客户端收到收面的信令后表示成功建立连接,接下来要发送给远程主机的数据就可以发送给代理服务器了,代理服务器建立连接后会在根据IP地址和端口号对应的连接放入缓存,收到信令后再根据IP地址和端口号从缓存中找到对应的连接,将数据通过该连接转发出去。

下面说一下base64这种编码方式,它还是很常用的,优点是使用起来简单,缺点是编码效率低劲(每3个字节会编码成4个字节),安全性差(这个刚才已经说过了)。

以前用.NET和java的时候形形色色的编码加密散列都见过也差不多都用过,不过高级语言会提供相应的类,一两句话就可以实现加密解密,当然在C里面也可以用OpenSSL来实现,同样简单,飞信里面也用到了base64编码,就是在2010版本里面要求输入图片验证码的时候用到的,服务器端会将生成的图片验证码图片的二进制数据流进行base64编码,以明文的方式放到xml中发送给客户端,客户端对其进行解码,然后就可以得到验证码图片(还是觉得这个过程有点多此一举)。

今天看了一下base64的编码规则,把3个8位字节(3*8=24)转化为4个6位的字节(4*6=24),之后在6位的前面补两个0,形成8位一个字节的形式,这样就形成了4个8位的字节,然后再将每个8位字节转换成10进制,再从编码表中找到对应的字符就可以了,编码的时候3个字节一组进行编码,当不足3个字节的时候,进行完上面的操作后,将剩余的位用0补齐(注意:并不是补到32位),于是2个字节编成3个字节,不足四个字节的用’='补齐,哈,感觉自己写得乱七八糟的,不过这种东西在网上一搜一大片,也没有必要细说了,另外附上刚才写的一个编码的小程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
 #include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
char* base64_encode(const char *src);
 
int main(int argc , char *argv[])
{
	const char in[] = "levin";
	char *res = base64_encode(in);
	printf("%s\n" , res);
	free(res);
	return 0;
}
char* base64_encode(const char *src){
 
	char* dest;
	char in[4];
	long buf = 0 , tmp = 0;
	int i = 0 , j = 0 , count = 0;
	char table[] = {
	    'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G'
	  , 'H' , 'I' , 'J' , 'K' , 'L' , 'M' , 'N'
	  , 'O' , 'P' , 'Q' , 'R' , 'S' , 'T' , 'U'
	  , 'V' , 'W' , 'X' , 'Y' , 'Z' , 'a' , 'b'
	  , 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , 'i'
	  , 'j' , 'k' , 'l' , 'm' , 'n' , 'o' , 'p'
	  , 'q' , 'r' , 's' , 't' , 'u' , 'v' , 'w'
	  , 'x' , 'y' , 'z' , '0' , '1' , '2' , '3'
	  , '4' , '5' , '6' , '7' , '8' , '9' , '+'
	  , '/' , '='
	};
 
	count = strlen(src) / 3 + (strlen(src) % 3 == 0 ? 0 : 1);
	dest = (char*)malloc(count * 4 + 1);
 
	for( ; j < count ; j++){
 
		bzero(in , sizeof(in));
		strncpy(in , src + j * 3 , 3 );
 
		buf = 0 , buf = 0 , i = 0; 
 
		for(; i < strlen(in) ; i++){
			tmp = (long)in[i];
			tmp <<= (16 - i * 8);
			buf |= tmp;
		}
 
		for(i = 0; i < 4 ; i ++){
			if(strlen(in) + 1 > i){
				tmp = buf >> (18 - i * 6);
				tmp &= 0x3F;
				dest[j * 4 + i] = table[tmp];
			}else{
				dest[j * 4 + i] = '=';
			}
		}
	}
	return dest;
}

上面的程序会输出bGV2aW4=,因为最后一组不足3字节,用=补了一个字节。

程序很简单,写它纯粹是为了强化一下对base64编码规则的记忆,解码的就不写了。

我程序里面是直接用OpenSSL来干的,少量数据进行编码的时候一个函数就够了,很方便:

1
EVP_EncodeBlock(out , (unsigned char*)in , strlen(in));

分类: 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++ 标签:

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++ 标签: ,