sprintf()函数注意项

本文详细介绍了C语言中的sprintf和snprintf函数的使用,包括格式化字符串、连接字符串、打印地址信息以及如何避免缓冲区溢出等问题。强调了snprintf的安全性,因为它能防止缓冲区溢出。同时,通过示例展示了如何利用这两个函数的返回值以及动态指定格式的技巧。文章还提醒了使用sprintf时常见的错误,如忘记第一个参数、变参对应错误等。
摘要由CSDN通过智能技术生成

1. 格式化字符串

 int sprintf( char *buffer, const char *format [, argument] … );

//注意一下返回值 为 int 是格式化好后的字符串的长度

注意,第一个参数不能传递 string类型的,若是结果非要string类,则可以

char c[size];

sprintf(c, "格式化形式",要格式化的内容);

string s = c;

2. 连接字符串

sprintf的格式控制串中既然可以插入各种东西,并最终把它们“连成一串”,自然也就能够连接字符串,从而在许多场合可以替代 strcat,但 sprintf 能够一次连接多个字符串(自然也可以同时在它们中间插入别的内容,总之非常灵活)。比如:

1

2

3

4

char *who = "I";

char *whom = "52PHP";

sprintf(s, "%s love %s.", who, whom); // 产生:"I love 52PHP. "

strcat 只能连接字符串(一段以 '\0' 结尾的字符数组或叫做字符缓冲,null-terminated-string),但有时我们有两段字符缓冲区,他们并不是以 '\0' 结尾。比如许多从第三方库函数中返回的字符数组,从硬件或者网络传输中读进来的字符流,它们未必每一段字符序列后面都有个相应的 '\0' 来结尾。如果直接连接,不管是 sprintf 还是 strcat 肯定会导致非法内存操作,而 strncat 也至少要求第一个参数是个 null-terminated-string,那该怎么办呢?我们 自然会想起前面介绍打印整数和浮点数时可以指定宽度,字符串也一样的。比如:

1

2

char a1[] = {'A''B''C''D''E''F''G'};

char a2[] = {'H''I''J''K''L''M''N'};

如果:

1

sprintf(s, "%s%s", a1, a2); // Don’t do that!

十有八九要出问题了。是否可以改成:

1

sprintf(s, "%7s%7s", a1, a2);

也没好到哪儿去,正确的应该是:

1

sprintf(s, "%.7s%.7s", a1, a2); // 产生:"ABCDEFGHIJKLMN"

这可以类比打印浮点数的“%m.nf”,在“%m.ns”中,m表示占用宽度(字符串长度不足时补空格,超出了则按照实际宽度打印),n才表示从相应的字符串中最多取用的字符数。通常在打印字符串时m没什么大用,还是点号后面的n用的多。自然,也可以前后都只取部分字符

1

sprintf(s, "%.6s%.5s", a1, a2); // 产生:"ABCDEFHIJKL"

在许多时候,我们或许还希望这些格式控制符中用以指定 长度信息的数字是动态的,而不是静态指定的,因为许多时候,程序要到运行时才会清楚到底需要取字符数组中的几个字符,这种动态的宽度/精度设置功能在 sprintf 的实现中也被考虑到了,sprintf 采用“*”来占用一个本来需要一个指定宽度或精度的常数数字的位置,同样,而实际的宽度或精度就可以 和其它被打印的变量一样被提供出来,于是,上面的例子可以变成:

1

sprintf(s, "%.*s%.*s", 7, a1, 7, a2);

或者:

1

sprintf(s, "%.*s%.*s"sizeof(a1), a1, sizeof(a2), a2);

实际上,前面介绍的打印字符、整数、浮点数等都可以动态指定那些常量值,比如:

1

2

3

sprintf(s, "%-*d", 4, 'A');             // 产生 "65 "

sprintf(s, "%#0*X", 8, 128);            // 产生 "0X000080″,"#"产生0X

sprintf(s, "%*.*f", 10, 2, 3.1415926);  // 产生"      3.14″

3、打印地址信息

有时调试程序时,我们可能想查看某些变量或者成员的地址,由于地址或者指针也不过是个32位的数,你完全可以使用打印无符号整数的“%u”把他们打印出来:

1

sprintf(s, "%u", &i);

不过通常人们还是喜欢使用16进制而不是10进制来显示一个地址:

1

sprintf(s, "%08X", &i);

然而,这些都是间接的方法,对于地址打印,sprintf 提供了专门的“%p”:

1

sprintf(s, "%p", &i);

我觉得它实际上就相当于:

1

sprintf(s, "%0*x", 2 * sizeof(void *), &i);

4、利用sprintf的返回值

较少有人注意 printf/sprintf 函数的返回值,但有时它却是有用的,spritnf 返回了本次函数调用最终打印到字符缓冲区中的字符数目。也就是说每当一次 sprinf 调用结束以后,你无须再调用一次strlen 便已经知道了结果字符串的长度。如:

1

int len = sprintf(s, "%d", i);

对于正整数来说,len便等于整数i的10进制位数。

下面的是个完整的例子,产生10个[0, 100)之间的随机数,并将他们打印到一个字符数组s中,以逗号分隔开。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

#include <stdio.h>

#include <stdlib.h>

#include <time.h>

int main(int argc, char **argv)

{

    srand(time(0));

    char s[64];

    int offset = 0;

    for (int i = 0; i < 10; i++)

    {

        offset += sprintf(s + offset, "%d, "rand() % 100);

    }

    s[offset - 2] = '\n'; // 将最后一个逗号换成换行符。因为"%d, ",因为offset指向最

    printf(s);         // 结尾的下一位,-1时是空格位,-2才是,        

    return 0;

}

设想当你从数据库中取出一条记录,然后希望把他们的各个字段按照某种规则连接成一个字符串时,就可以使用这种方法,从理论上讲,他应该比不断的 strcat 效率高,因为 strcat 每次调用都需要先找到最后 的那个 '\0' 的位置,而在上面给出的例子中,我们每次都利用 sprintf 返回值把这个位置直接记下来了。

6、使用 sprintf 的常见问题

sprintf是个变参函数,使用时经常出问题,而且只要出问题通常就是能导致程序崩溃的内存访问错误,但好在由sprintf误用导致的问题虽然严重,却很容易找出,无非就是那么几种情况,通常用眼睛再把出错的代码多看几眼就看出来了。

1. 缓冲区溢出

第一个参数的长度太短了,没的说,给个大点的地方吧。当然也可能是后面的参数的问题,建议变参对应一定要细心,而打印字符串时,尽量使用“%.ns”的形式指定最大字符数。

2.忘记了第一个参数

低级得不能再低级问题,用 printf 用得太惯了。// 偶就常犯。

3.变参对应出问题

通常是忘记了提供对应某个格式符的变参,导致以后的参数统统错位,检查检查吧。尤其是对应“*”的那些参数,都提供了吗?不要把一个整数对应一个“%s”,编译器会觉得你欺她太甚了。


二、snprintf() 详解

函数原型:

int snprintf(char *restrict buf, size_t n, const char * restrict  format, ...);

函数说明:最多从源串中拷贝 n-1 个字符到目标串中,然后再在后面加一个 '\0'。所以如果目标串的大小为 n 的话,将不会溢出。

函数返回值:若成功则返回欲写入的字符串长度,若出错则返回负值。

1、推荐的用法

1

2

3

4

5

6

7

8

9

10

#include <stdio.h>

int main(int argc, char **argv)

{

    char str[10] = {0,};

    snprintf(str, sizeof(str), "0123456789012345678");

    printf("str=%s\n", str);

    return 0;

}

输出:

str=012345678

2、不推荐的用法

1

2

3

4

5

6

7

8

9

10

#include <stdio.h>

int main(int argc, char **argv)

{

    char str[10] = {0,};

    snprintf(str, 18, "0123456789012345678");

    printf("str=%s/n", str);

    return 0;

}

输出:

str=01234567890123456

3、snprintf() 函数返回值的测试:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

#include <stdio.h>

#include <string.h>

int main(int argc, char **argv)

{

    char str1[10] = {0,};

    char str2[10] = {0,};

    int ret1 = 0, ret2 = 0;

    ret1 = snprintf(str1, sizeof(str1), "%s""abc");

    ret2 = snprintf(str2, 4, "%s""aaabbbccc");

    printf("aaabbbccc length=%d\n"strlen("aaabbbccc"));

    printf("str1=%s,ret1=%d\n", str1, ret1);

    printf("str2=%s,ret2=%d\n", str2, ret2);

    return 0;

}

输出:

aaabbbccc length=9
str1=abc,ret1=3
str2=aaa,ret2=9

特别注意:

snprintf() 的返回值是欲写入的字符串长度,而不是实际写入的字符串度。如:

1

2

3

4

5

6

7

8

9

10

#include <stdio.h>

int main(int argc, char **argv)

{

    char test[8];

    int ret = snprintf(test, 5, "1234567890");//即 ret = "1234567890";因为你有

    printf("%d | %s\n", ret, test);       // 那么多,所有它欲写入的也是那么多

    return 0;

}

输出:

10 | 1234

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值