分类: C/C++
2010-11-15 19:23:47
关于sprintf和snprintf的正确使用。
sprintf ,snprintf是将字串格式化。 sprintf 用法例子:
可以指定宽度,右对齐,不足的左边补空格:
sprintf(s, "%8d", 123); //产生:" 123"
右对齐,不足的左边补0:
sprintf(s, "%08X", 4567); //产生:"000011D7"
当然也可以左对齐: sprintf(s, "%-8d", 123); //产生:"123 " sprintf 的格式控制串中既然可以插入各种东西,并最终把它们“连成一串”,自然也就能够连接字符串,但不能直接连接没有以' '结尾的字符数组,如果直接连接,肯定会导致非法内存操作,那该怎么办呢?我们自然会想起前面介绍打印整数和浮点数时可以指定宽度,字符串也一样的。比如: char a1[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
char a2[] = {'H', 'I', 'J', 'K', 'L', 'M', 'N'};
如果:sprintf(s, "%s%s", a1, a2); //Don't do that! 十有八九要出问题了。是否可以改成: sprintf(s, "%7s%7s", a1, a2);
也没好到哪儿去,正确的应该是: sprintf(s, "%.7s%.7s", a1, a2);//产生:"ABCDEFGHIJKLMN" 这可以类比打印浮点数的”%m.nf”,在”%m.ns”中,m 表示占用宽度(字符串长度不足时补空格,超出了则按照实际宽度打印),n 才表示从相应的字符串中最多取用的字符数。通常在打印字符串时m 没什么大用,还是点号后面的n 用的多。自然,也可以前后都只取部分字符: sprintf(s, "%.6s%.5s", a1, a2);//产生:"ABCDEFHIJKL" 注意:这里要注意一个符号扩展的问题:比如,假如我们想打印短整数(short)-1 的内存16 进制表示形式,在Win32 平台上,一个short 型占2 个字节,所以我们自然希望用4 个16 进制数字来打印它:
short si = -1;
sprintf(s, "%04X", si);
产生“FFFFFFFF”,怎么回事?因为spritnf
是个变参函数,除了前面两个参数之外,后面的参数都不是类型安全的,函数更没有办法仅仅通过一个“%X”就能得知当初函数调用前参数压栈时被压进来的到底
是个4 字节的整数还是个2 字节的短整数,所以采取了统一4 字节的处理方式,导致参数压栈时做了符号扩展,扩展成了32 位的整数-1,打印时4
个位置不够了,就把32 位整数-1 的8 位16 进制都打印出来了。
如果你想看si 的本来面目,那么就应该让编译器做0 扩展而不是符号扩展(扩展时二进制左边补0 而不是补符号位):
sprintf(s, "%04X", (unsigned short)si);
就可以了。 sprintf() 有时很危险 例子:
void f(const char *p) { char buf[11]={0}; sprintf(buf,"%10s",p); // very dangerous printf("%sn",buf); } 不要让格式标记“%10s”误导你。如果p的长度大于10个字符,那么 sprintf() 的写操作就会越过buf的边界,从而产生一个缓冲区溢出。 检测这类缺陷并不容易,因为它们只在 p 的长度大于10个字符的时候才会发生。黑客通常利用这类脆弱的代码来入侵看上去安全的系统。 要修正这一缺陷,可以使用函数 snprintf()代替函数sprintf()。 函数原型:int snprintf(char *dest, size_t n, const char *fmt, ...); 函数说明: 最多从源串中拷贝n-1个字符到目标串中,然后再在后面加一个0。所以如果目标串的大小为n的话,将不会溢出。 函数返回值: 若成功则返回存入数组的字符数,若编码出错则返回负值。 推荐的用法: void f(const char *p) { char buf[11]={0}; snprintf(buf, sizeof(buf), "%10s", p); // 注意:这里第2个参数应当用sizeof(str),而不要使用硬编码11,也不应当使用sizeof(str)-1或10 printf("%sn",buf); } strcpy 函数操作的对象是 字符串,完成 从 源字符串 到 目的字符串 的 拷贝 功能。 snprintf 函数操作的对象 不限于字符串:虽然目的对象是字符串,但是源对象可以是字符 串、也可以是任意基本类型的数据。这个函数主要用来实现 (字符串或基本数据类型)向 字符串 的转换 功能。如果源对象是字符串,并且指定 %s 格式符,也可实现字符串拷贝功能。 memcpy 函数顾名思义就是 内存拷贝,实现 将一个 内存块 的内容复制到另一个 内存块 这一功能。内存块由其首地址以及长度确定。程序中出现的实体对象,不论是什么类型,其最终表现就是在内存中占据一席之地(一个内存区间或块)。因 此,memcpy 的操作对象不局限于某一类数据类型,或者说可 适用于任意数据类型,只要能给出 对象的起始地址和内存长度信息、并且对象具有可操作性即可。鉴于 memcpy 函数等长拷贝的特点以及数据类型代表的物理意义,memcpy 函数通常限于同种类型数据或对象之间的拷贝,其中当然也包括字符串拷贝以及基本数据类型的拷贝。 对于字符串拷贝来说,用上述三个函数都可以实现,但是其实现的效率和使用的方便程度不同:
对 于非字符串类型的数据的复制来说,strcpy 和 snprintf 一般就无能为力了,可是对 memcpy 却没有什么影响。但是,对于基本数据类型来说,尽管可以用 memcpy 进行拷贝,由于有赋值运算符可以方便且高效地进行同种或兼容类型的数据之间的拷贝,所以这种情况下 memcpy 几乎不被使用。memcpy 的长处是用来实现(通常是内部实现居多)对结构或者数组的拷贝,其目的是或者高效,或者使用方便,甚或两者兼有。 strcpy和memcpy功能上也有些差别: 比如: const char *str1="abc\0def"; char str2[7]; 首先用strcpy实现: strcpy(str2,str1) 得到结果:str2="abc";也就是说,strcpy是以'\0'为结束标志的。 再用memcpy实现: memset(str2,7); memcpy(str2,str1,7); 得到结果:str2="abc\0def"; 也就是说,memcpy是对内存区域的复制。当然,不仅能够复制字符串数组,而且能够复制整型数组等其他数组。 |