由C语言字符串解析方式带来的隐晦的程序设计问题

问题场景一:
要格式化输出一行文本,格式如下:首先是一个字符串的16进制表示,这一部分最长32个字符,接着是与左边16进制数据对应的文本,一个看似正常的演示程序如下:
#include  < stdio.h >
#include 
< string .h >

int  main()  {
    
char part_text[32], part_hex[32], whole[128= {0};
    
char *text = "A test";

    
// make the part_hex
    char *= part_hex;
    memset(part_hex, 
' 'sizeof(part_hex));
    
for(int i = 0; text[i]; i++{
        p 
+= sprintf(p, "%02X ", (unsigned char)text[i]);
    }

    
*= ' ';

    
// make the part_text
    sprintf(part_text, "%s", text);

    strcat(whole, part_hex);
    strcat(whole, part_text);
    puts(whole); 

    
return 0;
}

期望的输出结果是:
41 20 74 65 73 74               A test
但实际上的输出结果却变成了:
41 20 74 65 73 74               A testA test
问题出在什么地方呢,就在于C语言对字符串的解析方式,这个例子是一个正面的例子,是说如果一个字符数组的最后一个元素不是'/0',那么把这个数组作为一个字符串解析的时候,程序不会在数组的最后一个元素处终止,而会一直解析下去,直到遇到一个'/0'。下面的问题场景二则是说如果字符数组中提前出现了一个'/0',程序又会在这个地方终止对数组的解析。问题很简单,但发现的过程挺痛苦。
继续上面那个例子,为了找出问题,调试到 char* p = part_hex;这一句,查看内存:
此时,各变量的地址分别是:
whole: 0012FEB0----0012FF2F
part_hex: 0012FF30---0012FF4F
part_text: 0012FF50----0012FF6F
0012FEB0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0012FEC0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0012FED0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0012FEE0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0012FEF0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0012FF00  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0012FF10  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0012FF20  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0012FF30  CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC  烫烫烫烫烫烫烫烫
0012FF40  CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC  烫烫烫烫烫烫烫烫
0012FF50  CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC  烫烫烫烫烫烫烫烫
0012FF60  CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC  烫烫烫烫烫烫烫烫
如上,在程序中内存分配情况应该是:
从低地址到高地址,依次是:指针p,数组whole,数组part_hex,数组part_text。
指针text所指字符串是常量,不存放在函数的临时栈中。因为intel的机器中栈是
从高地址向低地址生长,(在PUSH一个变量时ESP是减小),在函数的临时栈中也是如此。
按顺序存储遇到的临时变量。
上面的内存中,0012FEB0处存放着数组whole,它占128个字节,所以结束位置是0012FF2F。
从下一个地址0012FF30处开始存放数组part_hex,它占32个字节,结束位置是0012FF4F,
从下一个地址0012FF50开始存放数组part_text,它占32个字节,结束位置是0012FF6F。
现在随着程序的运行,运行到*p = ' '处停下来,观察内存:
0012FEB0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0012FEC0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0012FED0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0012FEE0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0012FEF0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0012FF00  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0012FF10  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0012FF20  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0012FF30  34 31 20 32 30 20 37 34 20 36 35 20 37 33 20 37  41 20 74 65 73 7
0012FF40  34 20 00 20 20 20 20 20 20 20 20 20 20 20 20 20  4 .            
0012FF50  CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC  烫烫烫烫烫烫烫烫
0012FF60  CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC  烫烫烫烫烫烫烫烫
看到part_hex(0012FF30)处已经存储着"A test"的每个字符的十六进制数据了。在
0012FF42处是一个0,这是因为执行了最后一个sprintf。这里的*p = ' '就是为了
让字符串宽度是32个字符。
继续运行,执行完sprintf(part_text, "%s", text);这一句。再看内存:
0012FEB0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0012FEC0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0012FED0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0012FEE0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0012FEF0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0012FF00  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0012FF10  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0012FF20  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0012FF30  34 31 20 32 30 20 37 34 20 36 35 20 37 33 20 37  41 20 74 65 73 7
0012FF40  34 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20  4              
0012FF50  41 20 74 65 73 74 00 CC CC CC CC CC CC CC CC CC  A test.烫烫烫烫.
0012FF60  CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC  烫烫烫烫烫烫烫烫
看到part_text(0012FF50)处已经存放着"A test."这个字符串了。
然后现在执行strcat(whole, part_hex);
part_hex的起始地址是0012FF30,但是,它的结束地址这时已经不是0012FF4F了。因为
C语言中'/0'才表示字符串的结束,所以strcat会从0012FF30处一直往后走,直到遇到一
个'/0'为止,而这个'/0'现在不在0012FF4F处,而在0012FF56处,这里已经是
part_text的地盘了,但编译器不会知道。
所以,part_hex就变成了:41 20 74 65 73 74               A test
至此,问题彻底明白了,改正则很简单。

问题二:
有字符数据:
    unsigned char data[] = {
        //0x3f, 0x37,
        //0x0a, 0xd4, 0x0a,0xd4,0x3f,0x0a,0xd5,
        //0x15, 0x06, 0x05, 0x00, 0x00, // adc eax, 0x0506
        0x66, 0x15, 0x00, 0x03    // adc ax, 0x0300
    };
如果执行
char temp[16];
strncpy(temp, data, 16);
出乎意料,执行strncpy后temp中的内容是:
66 15 00 00 00 00 00 00 00 00 00 00 00 00 00 00
而不是期望的:
66 15 00 03 00 00 00 00 00 00 00 00 00 00 00 00
这是因为strncpy函数遇到了data数组中的0x15后面的那个0,就会结束,而把剩余的填充为0。

得到的教训:对于内存的 raw data 这样的数据操作,一定要使用内存操作函数memXXX系列,如memset,
memcpy,memmove等,字符数组与字符串是有极大区别的。库函数的声明已经暗示了这一点,如strXXX系列的字符串处理函数,其参数类型都是char*和const char*的,而memXXX系列的函数,参数则都是void*的。
想到这里,自然会知道,要想得到一个字符数组的大小,则要使用sizeof,而不能使用strlen。要得到这个字符数组中第一个字符串的大小,则应该使用strlen。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值