详解snprintf以及最佳用法探索

本文详细介绍了C语言中的snprintf函数,包括其功能、注意事项和返回值的含义。强调了snprintf在字符串长度控制上的安全性,与strncpy的区别,以及在不同情况下可能出现的问题。还提出了在不同平台和编译器上使用snprintf时需要注意的兼容性问题,建议读者根据实际需求谨慎使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • 函数原型

include <stdio.h>

int snprintf(char *str, size_t size, const char *format, ...);

str: 目的内存空间地址;

size: 最大可用的目的内存空间大小;

format:格式化字符串(类似printf);

... : 可选参数,与format中的占位符相关(类似printf).

  • 函数功能

该函数可以将不同的内容(ex. int)按照期望的既定格式打印成字符串,并保存到buffer中。

比如需要将十进制数 20 转换为两bytes十六进制字符串的时候,可以使用该函数实现。

int ret;
char hex[10];
ret = snprintf(hex, sizeof(hex), "0X%04X", 20);

"0X%04X" 表示会先加上0X前缀,通常用来表示是十六进制;后面%04X是指按大写十六进制打印,且需要占据4位字符的宽度,不够4位时补0。所以最终的字符串格式会是“0X0014”。

  • 特别注意

1. 无论何时一定会在字符串末尾添加'\0' 字符串结束符。

  • 当字符串长度小于size时,会直接在字符串的末尾添加'\0';
  • 当字符串长度等于size时,会取前size-1个字符,并在末尾添加‘\0’(即:加上'\0'后,一共size个字符);
  • 当字符串长度大于size时,只取前size-1个字符,并在末尾添加'\0'(即:加上'\0'一共size个字符);

综上:snprintf不仅一定会自动在字符串末尾添加'\0',而且一定会尊重指定长度size的大小,不会超过该长度(算上自动在末尾增加的'\0')。所以从这一点来讲,使用snprintf是相对比较安全的,不用担心会出现overflow的情况。 

 NOTE:这一点上,snprintf与strncpy是有区别的。strncpy虽然也会指定目的内存的大小,但是当源字符串长度大于指定的内存大小时,strncpy虽然在达到指定大小后会停止copy,但是并不会在末尾自动添加'\0'。这样,在访问字符串时,有可能出现overflow的情况,因为末尾没有字符串结束符'\0'。

思考:

(1)当目的内存实际长度为N1,但是在使用snprintf时指定的实际size(第二个参数)为N2, 且N2<N1,可能会出现什么情况?

(2)如果N2>N1,又可能出现什么情况?

2. 特别小心返回值。

  • 当出现错误时,会返回负值;
  • 当字符串长度小于size时,返回打印到目的内存的实际字符串长度(不包括'\0');
  • 当字符串长度大于等于size时,尽管字符串会被截断(只有size-1个字符串被打印到目的内存),但是返回值却会返回源字符串的实际长度(即假设目的内存无限大,总是能写下源字符串)。

综上:snprintf的返回值可能大于或等于指定的size,这时候说明目的内存不够大,源字符串被截断,需要小心处理,这是否是期望发生的情形。

NOTE:有一些写法会利用snprintf的这个特点来确定合适的目的内存大小,例如:

const char *fmt = "sqrt(2) = %f";
char * buf;
int sz = snprintf(NULL, 0, fmt, sqrt(2));
buf = (char *)malloc(sz+1);     // +1 for append '\0'
snprintf(buf, sz+1, fmt, sqrt(2));
  •  实践

在实际使用snprintf函数时,应该根据自己的实际需求,来判断如何合理的使用。

通常的用法会类似如下。

include <stdio.h>
include <stdlib.h>

#define BUF_LEN 20
char buf[BUF_LEN];
int year = 2019;
int month = 12;
int day = 13;
int ret;
ret = snprintf(buf, sizeof(buf), "%d-%02d-%02d", year, month, day);
if(ret <0) {
    // error handling
} else if(ret >= sizeof(buf)) {
    // buffer isn't enough, error handling
} else {
    // expected case
}
  • 更进一步

snprintf是一个在C99才被加入如标准的函数,原来的各个编译器都有自己的实现,至少.NET2003编译器还要是使用_snprintf这样的函数名称。而这些编译器间都有差异,而且Glibc库又有自己的不同的实现。如果使用一些比较旧的编译器或者不同平台的编译器,你会发现,如果传递的buf的长度不够的情况下,有可能null-terminator都没有加入,那么你使用的时候还是可能溢出,而且返回值的判断在不同的平台也可能不一样。

所以如果你想编一个可以跨平台的安全且正确的snprintf,应该怎么做呢?

以下只是一个可能的参考。

#define MAX_LEN 32
char buffer[MAX_LEN];
char* source = "xxxxxxx";
size_t buf_len = sizeof(buffer) - 1;
int len = snprintf(buffer, buf_len, "%s", source);
    if ((len < 0) || (len > max_len))
    {
        // error handling
        ....
        printf("error or overflow!\n");
    }
    else
    {
        // expected case
        buffer[max_len] = 0;
    }

 

参考文章:

[1] https://linux.die.net/man/3/snprintf

[2] http://joequery.me/code/snprintf-c/

[3] https://cloud.tencent.com/developer/article/1546579

[4] https://cloud.tencent.com/developer/article/1021143

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值