Browsed by
月度归档: 2018年11月

sprintf 段错误

sprintf 段错误

最近迁移代码测试功能的时候发现一个段错误,打印堆栈显示std::string析构时段错误。最后分析原因:sprintf造成字符数组内存溢出,踩内存,导致附近std::string对象被改修,析构时产生段错误。

原代码

if(response_code == 200)
{
	sprintf(temp,"HTTP请求成功[%s]", strResponce.c_str());
	dispPlayMsg("HTTP请求",index,temp,WEB_LOG);
	
	return TRUE;
}

strResponce为http的响应结果,平时的都是本公司的soap服务,返回
字节数都很少,测试时的服务返回长度超过temp长度,溢出,踩内存。

修改后

if(response_code == 200)
{
	//sprintf易造成越界,导致段错误
	snprintf(temp, sizeof(temp), "HTTP请求成功[%s]", strResponce.c_str());
	dispPlayMsg("HTTP请求",index,temp,WEB_LOG);

	return TRUE;
}

慎用sprintf,尽量使用snprintf。
原来工作中就遇到过sprintf造成的段错误。

printf/sprintf/NULL 空指针问题

printf/sprintf/NULL 空指针问题

最近从windows到linux 迁移代码,遇到了一个sprintf参数空指针段错误。

很疑惑,按说项目这么久不该出现段错误。

1.代码

原代码(用到msxml)

sprintf(pItemData.szNodeText,"%s",(const char*)nodeptr->Gettext());

移植后的代码(用到libxml2)

xmlCharTemp = xmlNodeGetContent(xmlChildNode);

sprintf(pItemData.szNodeText,"%s", (const char*)xmlCharTemp);

xmlFree(xmlCharTemp);

用什么xml解析库并不关键,关键是sprintf的参数都有可能是空指针。

于是乎实验了一下,发现vc++6.0 和 g++编译器存在差异性。

 

 

Windows-vc++6.0-sprintf截图

 

 

Linux-g++sprintf-截图

2.G++为什么会出现这种情况?

输出汇编代码

g++ test.cpp -S -o test.S

 .file "test.cpp"
.section .rodata
.LC0:
.string "%s"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movq $0, -8(%rbp)
leaq -32(%rbp), %rax
movl $0, %edx
movl $.LC0, %esi
movq %rax, %rdi
movl $0, %eax
call sprintf
leaq -32(%rbp), %rax
movq %rax, %rdi
call puts
movq -8(%rbp), %rdx
leaq -32(%rbp), %rax
movq %rdx, %rsi
movq %rax, %rdi
call strcpy
leaq -32(%rbp), %rax
movq %rax, %rdi
call puts
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-28)"
.section .note.GNU-stack,"",@progbits


会发现两次调用sprintf,第一次调用的call sprintf,第二次调用的call strcpy。

3.printf也会存在类似的段错误

Windows-vc++6.0-printf截图

 

Linux-g++-printf-截图

 

g++汇编代码

.file "test2.cpp"
.section .rodata
.LC0:
.string "%s"
.LC1:
.string "%s \n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
movq $0, -8(%rbp)
movq -8(%rbp), %rax
movq %rax, %rsi
movl $.LC0, %edi
movl $0, %eax
call printf
movq -8(%rbp), %rax
movq %rax, %rsi
movl $.LC1, %edi
movl $0, %eax
call printf
movq -8(%rbp), %rax
movq %rax, %rdi
call puts
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-28)"
.section .note.GNU-stack,"",@progbits

会发现前两次调用的call printf,而第三次调用call puts。

 

4.总结

尽量不要期待编译器去做空指针的判断优化,因为大多数情况下你不知道编译器是怎么实现的,况且编译器间存在差距,自己保证参数的不为空指针。

 

最后代码无奈改成了这个样子。

sprintf(pItemData.szNodeText,"%s", xmlCharTemp!=NULL?(const char*)xmlCharTemp:"(null)");

参考:https://stackoverflow.com/questions/11589342/what-is-the-behavior-of-printing-null-with-printfs-s-specifier