c程序设计
libcurl和openssl的冲突和资源释放问题-curl_global_cleanup
[| 2014/09/26 00:59]
最近程序发现了个奇葩现象,即从文件载入的证书链用openssl校验是通过的,而用curl远程获取的就校验失败,错误码7。而这些内容写入文件在下一次程序启动时载入校验,又是成功的。
排查了各种内存泄露、不可见字符的可能性后。突然想起来是不是libcurl静态链接了openssl跟主程序动态链接的打架。查看后发现libcurl未静态链接openssl。
不过这也是一个启示。想起来既然curl也用到了openssl,那么它在最后cleanup的时候会不会把全局的openssl数据结构给释放掉。
尝试了一下,果然好了。原来我在一个函数里开头调用curl_global_init,结尾调用curl_global_cleanup。这样的话在程序结束时就会释放openssl的全局数据结构。导致后续调用证书校验报错。
不过openssl也不够友好,报证书签名错误,让人很难联想到是初始化问题。
不过aes加密部分并不受这个影响。
排查了各种内存泄露、不可见字符的可能性后。突然想起来是不是libcurl静态链接了openssl跟主程序动态链接的打架。查看后发现libcurl未静态链接openssl。
不过这也是一个启示。想起来既然curl也用到了openssl,那么它在最后cleanup的时候会不会把全局的openssl数据结构给释放掉。
尝试了一下,果然好了。原来我在一个函数里开头调用curl_global_init,结尾调用curl_global_cleanup。这样的话在程序结束时就会释放openssl的全局数据结构。导致后续调用证书校验报错。
不过openssl也不够友好,报证书签名错误,让人很难联想到是初始化问题。
不过aes加密部分并不受这个影响。
malloc、calloc、memset与free等内存操作的速度
[| 2012/05/30 00:55]
一直对内存操作的速度没有数值概念,只泛泛的知道memset影响效率,反复分配释放内存影响效率,具体速度如何,从来没试过,今天试验了一下。
写了个程序,分配一个指针数组,挨个分配内存,然后挨个释放。使用的是一台2核16g内存的虚拟机。gcc版本3.4.2
先是分配了1w个100字节块,发现5ms以内即可完成。
然后扩大到500字节块,速度没什么变化。
然后分配100w个字节块,平均需要270ms左右。
字节块扩大的2k左右,大概5s完成。
扩大到5k左右,大概12s左右。
以上数据均为malloc+memset数据和calloc数据,两者不相上下。
只malloc不memset,大概能缩短20%左右,可见memset对速度还是有一定影响的,不过对于性能要求不是那么严苛的程序,设置一下提高程序稳定性也是值得的。避免某变量忘记初始化出现野值的问题。
写了个程序,分配一个指针数组,挨个分配内存,然后挨个释放。使用的是一台2核16g内存的虚拟机。gcc版本3.4.2
先是分配了1w个100字节块,发现5ms以内即可完成。
然后扩大到500字节块,速度没什么变化。
然后分配100w个字节块,平均需要270ms左右。
字节块扩大的2k左右,大概5s完成。
扩大到5k左右,大概12s左右。
以上数据均为malloc+memset数据和calloc数据,两者不相上下。
只malloc不memset,大概能缩短20%左右,可见memset对速度还是有一定影响的,不过对于性能要求不是那么严苛的程序,设置一下提高程序稳定性也是值得的。避免某变量忘记初始化出现野值的问题。
printf空指针NULL的问题
[| 2012/05/16 17:52]
今天发现程序中有几个地方在没有校验指针为NULL的时候进行了打印日志操作,在字符串处理函数中使用NULL指针不是什么好事,研究了一下,发现在一些的linux发行版中,printf("%s",NULL)会打印一个“(NULL)”,而在solaris中则会出core,windows下也没有问题。
可见不同的c库对于这个行为的实现不太一致。
使用中还是要尽量避免这种用法,不过每次打日志前都要判断指针情况确实比较恶。
可见不同的c库对于这个行为的实现不太一致。
使用中还是要尽量避免这种用法,不过每次打日志前都要判断指针情况确实比较恶。
lua_tolstring导致调用lua_next时的lua PANIC问题
[| 2012/04/28 17:49]
前两天在用lua_next遍历一个lua表的时候遇到了:PANIC: unprotected error in call to Lua API (invalid key to 'next') 仔细检查了下代码和堆栈信息,发现没有问题,但为什么会说遍历失败呢?
找到文档看了下,原来lua_tolstring只支持number和string类型,但是对于number类型,在取值后也会转换其在表中的实际内容为string,而我遍历的表是使用默认自增索引作为key的,这样对key调用这个函数会导致key变成字符串,因而遍历有问题。
如果表的key不一定是string,而又要用lua_tolstring获取它的值,那么建议先在栈上复制一份,然后对于复制的值进行获取。
找到文档看了下,原来lua_tolstring只支持number和string类型,但是对于number类型,在取值后也会转换其在表中的实际内容为string,而我遍历的表是使用默认自增索引作为key的,这样对key调用这个函数会导致key变成字符串,因而遍历有问题。
如果表的key不一定是string,而又要用lua_tolstring获取它的值,那么建议先在栈上复制一份,然后对于复制的值进行获取。
c程序中变量在循环内部还是外部声明的问题
[| 2012/04/09 15:40]
忘记之前从哪看过的一个文章说不要在for、while等循环内声明变量,因为每次都会重复分配空间,很慢。
今天发现一个模块把变量声明都放到while里面了,看了下代码没有发现必须声明在里面的原因,于是开始怀疑是不是声明在内外是差不多的。
于是测试了一下:
int main() {
int i = 0;
for(;i < 10000000; i++) {
int b;
b++;
}
return 1;
}
使用gcc 编译,把int b放在循环内外试了试,用time ./a.out查看执行时间,发现用时基本相同。
添加-O2优化选项,执行时间均缩减到之前的1/3,内外两种方式时间依然相同。
定义了一个struct实验了下,结果相同
也就是说栈上元素的操作不必纠结于变量声明于何处。
尝试了下堆上元素操作,在预料之内:时间差距巨大,因为重复分配释放内存。
所以对于栈上元素,声明放在循环里和循环外是一样的。堆上元素不同,需注意。
另,仍然需要注意一些计算操作需要放在循环外,比如求大小之类的,避免循环的每个周期重复计算。
原因猜测:1, cpu对栈操作有优化,速度非常快。
2,编译器的基本优化中会优化(gcc没有使用-O参数时仍会优化)
具体原因待深究
今天发现一个模块把变量声明都放到while里面了,看了下代码没有发现必须声明在里面的原因,于是开始怀疑是不是声明在内外是差不多的。
于是测试了一下:
引用
int main() {
int i = 0;
for(;i < 10000000; i++) {
int b;
b++;
}
return 1;
}
使用gcc 编译,把int b放在循环内外试了试,用time ./a.out查看执行时间,发现用时基本相同。
添加-O2优化选项,执行时间均缩减到之前的1/3,内外两种方式时间依然相同。
定义了一个struct实验了下,结果相同
也就是说栈上元素的操作不必纠结于变量声明于何处。
尝试了下堆上元素操作,在预料之内:时间差距巨大,因为重复分配释放内存。
所以对于栈上元素,声明放在循环里和循环外是一样的。堆上元素不同,需注意。
另,仍然需要注意一些计算操作需要放在循环外,比如求大小之类的,避免循环的每个周期重复计算。
原因猜测:1, cpu对栈操作有优化,速度非常快。
2,编译器的基本优化中会优化(gcc没有使用-O参数时仍会优化)
具体原因待深究
静态库和动态库编译链接时的依赖检查
[| 2012/04/05 19:48]
今天编译lua程序时发现总是报缺少dlopen和pow之类的,需要手动加-lm -dl才行。感觉有点奇异,按我的理解来说使用静态库是不需要考虑依赖的。仔细研究了下,发现很多误区。
首先用静态库编译出的可执行文件是不考虑库依赖的,但并不代表用静态库时也不需要考虑。静态库在编译时是不检查函数是否被实现的,也就是说只需要.h即可。
其次,静态库和动态库其实区别很大,静态库只是编译出.o的一个打包的文件,而动态库添加了链接信息。
这样的话静态库还有一个特性,就是在静态库中可以调用一些预留接口,而把这些接口留待以后实现。
首先用静态库编译出的可执行文件是不考虑库依赖的,但并不代表用静态库时也不需要考虑。静态库在编译时是不检查函数是否被实现的,也就是说只需要.h即可。
其次,静态库和动态库其实区别很大,静态库只是编译出.o的一个打包的文件,而动态库添加了链接信息。
这样的话静态库还有一个特性,就是在静态库中可以调用一些预留接口,而把这些接口留待以后实现。
由strcm参数为NULL看编译器优化
[| 2012/02/14 18:29]
今天程序出了一个core,是strcmp的时候有一个参数没有判断为NULL导致的,当我编写了一个小程序:
测试的时候发现程序跑的毫无问题。
编译参数是gcc -g -O0,没有开任何优化。
gdb进去后发现strcmp根本没有被执行,改成int a = strcmp(NULL, "");后,出core了。
看来编译器默认还是提供一定优化的。
另外需要注意strcmp不会检查参数(效率考虑),所以需要自己检查。
引用
#include
int main()
{
strcmp(NULL, "“);
return 1;
}
int main()
{
strcmp(NULL, "“);
return 1;
}
测试的时候发现程序跑的毫无问题。
编译参数是gcc -g -O0,没有开任何优化。
gdb进去后发现strcmp根本没有被执行,改成int a = strcmp(NULL, "");后,出core了。
看来编译器默认还是提供一定优化的。
另外需要注意strcmp不会检查参数(效率考虑),所以需要自己检查。
由一个小概率出core看函数声明的重要性
[| 2012/01/14 23:39]
最近程序遇到一个小概率出core的bug,高压力下大概10分钟左右就会出core,gdb查看发现一个指针高四字节被置0xffffffff了,低四字节正常。
该指针是局部变量,存放在栈上,排除了线程间同步互斥写坏数据的可能。
该指针前后变量均正常,都是指针,排除了写越界的可能。
通过日志查看,在返回该指针的函数返回前,指针正常,返回后高四字节即被置0xffffffff了。推断应该是函数返回的过程中遭到了破坏。
但不知道为什么返回的过程中会出错,请教了下组内高工,高人就是高人,一听问题描述就表示应该是函数声明的问题。
原来,在调用另一个so文件中的函数时,如果没有该函数的声明,由于从该so的符号表里可以找到函数,所以编译可以通过,但gcc会把这个函数返回值按默认的int处理,这种情况下,32位机编译的程序是没问题的,但64位机上指针是8字节,导致高四字节数据丢失。但返回的指针超过int值域时,高四字节数据丢失,导致指针被破坏。
所以函数声明还是不可或缺的。
该指针是局部变量,存放在栈上,排除了线程间同步互斥写坏数据的可能。
该指针前后变量均正常,都是指针,排除了写越界的可能。
通过日志查看,在返回该指针的函数返回前,指针正常,返回后高四字节即被置0xffffffff了。推断应该是函数返回的过程中遭到了破坏。
但不知道为什么返回的过程中会出错,请教了下组内高工,高人就是高人,一听问题描述就表示应该是函数声明的问题。
原来,在调用另一个so文件中的函数时,如果没有该函数的声明,由于从该so的符号表里可以找到函数,所以编译可以通过,但gcc会把这个函数返回值按默认的int处理,这种情况下,32位机编译的程序是没问题的,但64位机上指针是8字节,导致高四字节数据丢失。但返回的指针超过int值域时,高四字节数据丢失,导致指针被破坏。
所以函数声明还是不可或缺的。