snprintf函数用法及与sprintf比较

2023-05-16

int snprintf(char restrict buf, size_t n, const char restrict format, …); 
函数说明:最多从源串中拷贝n-1个字符到目标串中,然后再在后面加一个0。所以如果目标串的大小为n 的话,将不会溢出。 
函数返回值:若成功则返回欲写入的字符串长度,若出错则返回负值。

标准库函数原型如下
int printf(const char *format, .....)
int sprintf(char *str, const char *format, ....)
int snprintf(char *str,size_t size, const char *format, ....)
snprintf最多从源串中复制size-1 个字符到目标串中,然后再后面加一个\0, sprintf没有这样的限制,使用 snprintf更安全。
sprintf使用注意事项
(1)多个字符串连接,sprintf(s,"%.Ms %.Ns",a1,a2);其中M N 是正整数,分别指定复制a1中的M个字符,a2中N个字符输出到s中
(2)一定要申请较大的数组空间,避免出现堆栈的溢出,破坏堆栈
snprintf使用注意事项
(1) snprintf最多从源串中复制size-1 个字符到目标串中,返回值是源串字符的个数,而不是复制字符的个数
snprintf(buf,sizeof(buf),......)
打印内存的方法
printf("%p\n",&a)
printf("0x%x",&a)
格式化输出
printf("10.5s","hello,abcdefg")每行固定占10个字符并且右对齐,显示5个字符
printf("%-10.5s","hello,abcdefg")每行固定占10个字符并且左对齐,显示5个字符


今天在项目中使用snprintf时遇到一个比较迷惑的问题,追根溯源了一下,在此对sprintf和snprintf进行一下对比分析。



因为sprintf可能导致缓冲区溢出问题而不被推荐使用,所以在项目中我一直优先选择使用snprintf函数,虽然会稍微麻烦那么一点点。这里就是sprintf和snprintf最主要的区别:snprintf通过提供缓冲区的可用大小传入参数来保证缓冲区的不溢出,如果超出缓冲区大小则进行截断。但是对于snprintf函数,还有一些细微的差别需要注意。


snprintf函数的返回值


sprintf函数返回的是实际输出到字符串缓冲中的字符个数,包括null结束符。而snprintf函数返回的是应该输出到字符串缓冲的字符个数,所以snprintf的返回值可能大于给定的可用缓冲大小以及最终得到的字符串长度。看代码最清楚不过了:


char tlist_3[10] = {0};
    int len_3 = 0;


    len_3 = snprintf(tlist_3,10,"this is a overflow test!\n");
    printf("len_3 = %d,tlist_3 = %s\n",len_3,tlist_3);
上述代码段的输出结果如下:
len_3 = 25,tlist_3 = this is a
所以在使用snprintf函数的返回值时,需要小心慎重,避免人为造成的缓冲区溢出,不然得不偿失。
snprintf函数的字符串缓冲


int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
上面的函数原型大家都非常熟悉,我一直以为snprintf除了多一个缓冲区大小参数外,表现行为都和sprintf一致,直到今天遇上的bug。在此之前我把下面的代码段的两个输出视为一致。
char tlist_1[1024] = {0},tlist_2[1024]={0};
    char fname[7][8] = {"a1","b1","c1","d1","e1","f1","g1"};
    int i = 0, len_1,len_2 = 0;


    len_1 = snprintf(tlist_1,1024,"%s;",fname[0]);
    len_2 = snprintf(tlist_2,1024,"%s;",fname[0]);


    for(i=1;i<7;i++)
    {
        len_1 = snprintf(tlist_1,1024,"%s%s;",tlist_1,fname[i]);
        len_2 = sprintf(tlist_2,"%s%s;",tlist_2,fname[i]);
    }


    printf("tlist_1: %s\n",tlist_1);
    printf("tlist_2: %s\n",tlist_2);
可实际上得到的输出结果却是:
tlist_1: g1;
tlist_2: a1;b1;c1;d1;e1;f1;g1;
知其然就应该知其所以然,这是良好的求知态度,所以果断翻glibc的源代码去,不凭空想当然。下面用代码说话,这就是开源的好处之一。首先看snprintf的实现:
glibc-2.18/stdio-common/snprintf.c:
 18 #include <stdarg.h>
 19 #include <stdio.h>
 20 #include <libioP.h>
 21 #define __vsnprintf(s, l, f, a) _IO_vsnprintf (s, l, f, a)
 22
 23 /* Write formatted output into S, according to the format
 24    string FORMAT, writing no more than MAXLEN characters.  */
 25 /* VARARGS3 */
 26 int
 27 __snprintf (char *s, size_t maxlen, const char *format, ...)
 28 {
 29   va_list arg;
 30   int done;
 31
 32   va_start (arg, format);
 33   done = __vsnprintf (s, maxlen, format, arg);
 34   va_end (arg);
 35
 36   return done;
 37 }
 38 ldbl_weak_alias (__snprintf, snprintf)
使用_IO_vsnprintf函数实现:
glibc-2.18/libio/vsnprintf.c:
 94 int
 95 _IO_vsnprintf (string, maxlen, format, args)
 96      char *string;
 97      _IO_size_t maxlen;
 98      const char *format;
 99      _IO_va_list args;
100 {
101   _IO_strnfile sf;
102   int ret;
103 #ifdef _IO_MTSAFE_IO
104   sf.f._sbf._f._lock = NULL;
105 #endif
106
107   /* We need to handle the special case where MAXLEN is 0.  Use the
108      overflow buffer right from the start.  */
109   if (maxlen == 0)
110     {
111       string = sf.overflow_buf;
112       maxlen = sizeof (sf.overflow_buf);
113     }
114
115   _IO_no_init (&sf.f._sbf._f, _IO_USER_LOCK, -1, NULL, NULL);
116   _IO_JUMPS (&sf.f._sbf) = &_IO_strn_jumps;
117   string[0] = '\0';
118   _IO_str_init_static_internal (&sf.f, string, maxlen - 1, string);
119   ret = _IO_vfprintf (&sf.f._sbf._f, format, args);
120
121   if (sf.f._sbf._f._IO_buf_base != sf.overflow_buf)
122     *sf.f._sbf._f._IO_write_ptr = '\0';
123   return ret;
124 }
关键点出来了,源文件第117行string[0] = '\0';把字符串缓冲先清空后才进行实际的输出操作。那sprintf是不是就没有清空这个操作呢,继续代码比较中,sprintf的实现:
glibc-2.18/stdio-common/snprintf.c:
 18 #include <stdarg.h>
 19 #include <stdio.h>
 20 #include <libioP.h>
 21 #define vsprintf(s, f, a) _IO_vsprintf (s, f, a)
 22
 23 /* Write formatted output into S, according to the format string FORMAT.  */
 24 /* VARARGS2 */
 25 int
 26 __sprintf (char *s, const char *format, ...)
 27 {
 28   va_list arg;
 29   int done;
 30
 31   va_start (arg, format);
 32   done = vsprintf (s, format, arg);
 33   va_end (arg);
 34
 35   return done;
 36 }
 37 ldbl_hidden_def (__sprintf, sprintf)
 38 ldbl_strong_alias (__sprintf, sprintf)
 39 ldbl_strong_alias (__sprintf, _IO_sprintf)
使用_IO_vsprintf而不是_IO_vsnprintf函数,_IO_vsprintf函数实现:
glibc-2.18/libio/iovsprintf.c:
 27 #include "libioP.h"
 28 #include "strfile.h"
 29
 30 int
 31 __IO_vsprintf (char *string, const char *format, _IO_va_list args)
 32 {
 33   _IO_strfile sf;
 34   int ret;
 35
 36 #ifdef _IO_MTSAFE_IO
 37   sf._sbf._f._lock = NULL;
 38 #endif
 39   _IO_no_init (&sf._sbf._f, _IO_USER_LOCK, -1, NULL, NULL);
 40   _IO_JUMPS (&sf._sbf) = &_IO_str_jumps;
 41   _IO_str_init_static_internal (&sf, string, -1, string);
 42   ret = _IO_vfprintf (&sf._sbf._f, format, args);
 43   _IO_putc_unlocked ('\0', &sf._sbf._f);
 44   return ret;
 45 }
 46 ldbl_hidden_def (__IO_vsprintf, _IO_vsprintf)
 47
 48 ldbl_strong_alias (__IO_vsprintf, _IO_vsprintf)
 49 ldbl_weak_alias (__IO_vsprintf, vsprintf)
在40行到42行之间没有进行字符串缓冲的清空操作,一切了然。
一开始是打算使用gdb调试跟踪进入snprintf函数探个究竟的,可是调试时发现用step和stepi都进不到snprintf函数里面去,看了一下链接的动态库,原来libc库已经stripped掉了:


hong@ubuntu:~/test/test-example$ ldd snprintf_test
        linux-gate.so.1 =>  (0xb76f7000)
        libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7542000)
        /lib/ld-linux.so.2 (0xb76f8000)
hong@ubuntu:~/test/test-example$ file /lib/i386-linux-gnu/libc.so.6
/lib/i386-linux-gnu/libc.so.6: symbolic link to `libc-2.15.so'
lzhong@ubuntu:~/test/test-example$ file /lib/i386-linux-gnu/libc-2.15.so
/lib/i386-linux-gnu/libc-2.15.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), BuildID[sha1]=0x7a6dfa392663d14bfb03df1f104a0db8604eec6e, for GNU/Linux 2.6.24, stripped
所以只能去找 ftp://ftp.gnu.org/gnu/glibc官网啃源代码了。
在找glibc源码时,我想知道系统当前使用的glibc版本,一时不知道怎么查,Google一下大多数都是Redhat上的rpm查法,不适用于Ubuntn,而用dpkg和aptitude show都查不到glibc package,后来才找到ldd用法。


hong@ubuntu:~/test/test-example$ ldd --version
ldd (Ubuntu EGLIBC 2.15-0ubuntu20) 2.15
Copyright (C) 2012 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.
现在才发现Ubuntn用的是好像是EGLIBC,而不是标准的glibc库。其实上面ldd snprintf_test查看应用程序的链接库的方法可以更快速地知道程序链接的glibc版本。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

snprintf函数用法及与sprintf比较 的相关文章

  • Centos7上安装MySQL community步骤

    下载mysql80 community release el7 3 noarch rpm软件源包文件 MySQL Community Yum Repository Downloads shell gt sudo yum localinsta
  • Win10远程桌面账号正确但连接失败的解决方法(使用微软AzureAD的账号)

    百度到的那些改组策略之类的完全没用 xff0c 花了一个小时左右 xff0c 终于在谷歌找到了答案 xff01 https community spiceworks com topic 2048258 logon attempt faile
  • Ubuntu 20.04系统 安装显卡驱动RTX3080

    网络上很多 xff0c 试过很多前人的经验 xff0c 多多少少都有些奇怪问题特别是对于我这样的新手 我记录下 xff0c 仅代表我个人安装成功的经验 说明 xff0c 我是台式机安装Ubuntu 怎么安装完系统就不谈了 xff0c 主要说
  • SerDes---CDR技术

    1 为什么需要CDR 时钟数据恢复主要完成两个工作 xff0c 一个是时钟恢复 xff0c 一个是数据重定时 xff0c 也就是数据的恢复 时钟恢复主要是从接收到的 NRZ xff08 非归零码 xff09 码中将嵌入在数据中的时钟信息提取
  • Failed to start bean ‘documentationPluginsBootstrapper‘

    最近写项目发现导入Swagger2依赖会报错Failed to start bean documentationPluginsBootstrapper 这个错误是由Springfox的一个bug引起 xff0c 解决这个问题的方式有多种 x
  • mac,centos 安装Connector/Python

    mac 官网下载 xff0c 双击 centos https dev mysql com doc connector python en connector python installation source html sudo yum
  • 5个可保存的在线代码片段平台推荐-变成自己的代码词典库

    文章目录 1 谷歌等现代浏览器自带的代码片段2 github gitee gitcode等在线托管平台的gist列表3 各大在线IDE平台4 掘金代码片段5 在线笔记 1 谷歌等现代浏览器自带的代码片段 路径 xff1a 浏览器开发者工具
  • 前置声明与C++头文件互相包含导致的error: 'xxx' does not name a type问题

    在一个源文件中 xff0c 要声明或定义一个类的指针时 xff0c 必须在使用前声明或定义该类 xff0c 因此下面的代码会报错 xff1a span class hljs class span class hljs keyword cla
  • C语言中1<<n是什么意思

    1 lt lt 2 1的二进制为 0000 0001 左移2位 0000 0100 如果再转成10进制就是4
  • 位运算——左移和右移

    lt lt 左移 1 运算规则 xff1a 按二进制形式把所有的数字向左移动对应的位数 xff0c 高位移出 舍弃 xff0c 低位的 空位补零 2 语法格式 xff1a 需要移位的数字 lt lt 移位的次数 例如 xff1a 3 lt
  • 【Python】爬取TapTap原神评论并生成词云分析

    序言 本来是想爬B站的 xff0c 但是B站游戏区的评论好像是动态方式加载 xff0c 分析了一通没搞懂怎么爬 xff0c 所以转到了TapTap xff0c TapTap评论页通过URL来定位 xff0c 非常容易拼接URL去获取想要的页
  • ubuntu下http代理设置

    方法一 这是一种临时的手段 xff0c 如果您仅仅是暂时需要通过http 代理使用apt get xff0c 您可以使用这种方式 在使用apt get 之前 xff0c 在终端中输入以下命令 xff08 根据您的实际情况替换yourprox
  • ARM汇编(基于树莓派3B)3

    第七章 Linux操作系统服务 在第1章 入门 中 xff0c 我们需要能够退出程序并显示字符串 我们使用Raspbian Linux来执行此操作 xff0c 直接调用操作系统服务 在所有高级编程语言中 xff0c 都有一个运行时库 xff
  • VS修改平台工具集

    MSB8020 无法找到 Visual Studio 2010 的生成工具 平台工具集 61 v100 若要使用 v100 生成工具进行生成 xff0c 请安装 Visual Studio 2010 生成工具 或者 xff0c 可以升级到当
  • Web安全领域的探索之远程文件包含漏洞(LFI)

    文件包含渗透 File Inclusion 文件包含漏洞 xff1a 即File Inclusion xff0c 意思是文件包含 xff08 漏洞 xff09 xff0c 是指当服务器开启allow url include选项时 xff0c
  • EFI与MBR启动的区别

    EFI与MBR启动的区别 大硬盘和WIN8系统 xff0c 让我们从传统的BIOS 43 MBR模式升级到UEFI 43 GPT模式 xff0c 现在购买的主流电脑 xff0c 都是预装WIN8系统 xff0c 为了更好的支持2TB硬盘 x
  • mac install MySQL-python

    mac python mysqldb EnvironmentError mysql config not found save brew install mysql 先安装mysql 最后 pip install MySQL Python
  • 每天一个linux命令2---curl命令

    常用参数详解 参数描述 I head只显示传输文档 xff0c 经常用于测试连接本身 o output把输出写到该文件中 xff0c 必须输入保存文件名 O remote name把输出写到该文件中 xff0c 保留远程文件的文件名 F f
  • Linux CentOS7配置iptables端口映射将客户端与另一台主机内的虚拟机服务连接

    这是一篇配置用户主机端口访问Linux服务器内KVM虚拟机的记录文章 各位看官朋友有没有遇到过这种访问情况 自己有一台笔记本电脑A xff0c 还有一台主机B 自己把主机B刷成了linux服务器系统 xff0c 并且在服务器内安装创建了一台
  • Linux内核netfilter子系统ulogd项目性能调优记录

    使用ULOGD打SYSLOG到SYSLOG NG xff0c 当NFLOG拿到的数据包在6K左右时 xff0c CPU有两个核心占了15 左右使用修改过的ULOGD直接打TCP xff0c CPU只有一个核心占15 左右 xff08 修改版

随机推荐

  • LINUX如何设置numlock键开机状态

    第一步 xff1a 安装numlockx xff0c 输入命令 xff1a sudo yum install numlockx 第二步 xff1a 用 vim 打开 etc lightdm lightdm conf文件 xff0c 如果文件
  • Mybatis-plus扩展

    这玩意类似于mp自带的basemapper Copyright c 2011 2020 baomidou jobob 64 qq com lt p gt Licensed under the Apache License Version 2
  • Photoshop中的“磁性套索”背后算法的OpenCV实现

    本文由Markdown语法编辑器编辑完成 1 PS中的磁性套索工具简介 xff1a 本示例是基于Photoshop CS4的工具栏 xff1a 2 磁性套索工具背后的算法Livewire原理 xff1a 3 Livewire Segment
  • 轻量化网络mobileNet与ShuffleNet

    摘要 最近出了一篇旷视科技的孙剑团队出了一篇关于利用Channel Shuffle实现的卷积网络优化 ShuffleNet 我关注了一下 xff0c 原理相当简单 它只是为了解决分组卷积时 xff0c 不同feature maps分组之间的
  • 人脸比对(1:N)

    第 1章 前言 设计出人脸1 xff1a N xff0c 随着N的增大准确率降低最小的解决方案具有很强的现实意义 人脸1 xff1a N的框架大致分为 xff1a 人脸检测 人脸对齐 人脸映射与人脸识别LOSS的设计 xff0c 结构如下图
  • 商家招牌的分类与检测

    现实生活中的招牌各种各样 xff0c 千变万化 针对初赛 xff0c 在现实世界中 xff0c 选取100类常见的招牌信息 xff0c 如肯德基 麦当劳 耐克等 将最终的分类结果上传到比赛平台 数据典型图像 a 样本类别多样性 b 每类样本
  • TensorRT简介--高性能深度学习支持引擎

    上文简单提到了TensorRT的基础与onnx转换 xff1a 基于TensorRT的神经网络推理与加速 xff1a https blog csdn net intflojx article details 81712651 后面看到老板的
  • raspberry install mysql-connector-python

    wget http cdn mysql com Downloads Connector Python mysql connector python 1 0 11 zip unzip mysql connector python 1 0 11
  • ubuntu安装TensorFlow(支持不同版本)

    CUDA 43 CUDNN准备 xff1a 1 安装NVIDIA驱动 xff08 1 xff09 查询NVIDIA驱动 首先去官网 http www nvidia com Download index aspx lang 61 en us
  • 基于变换不变低秩纹理(TILT)的图像校正(附代码)

    原理简介 事实上 xff0c 对于未加旋转的图像 xff0c 由于图像的对称性与自相似性 xff0c 我们可以将其看作是一个带噪声的低秩矩阵 当图像由端正发生旋转时 xff0c 图像的对称性和规律性就会被破坏 xff0c 也就是说各行像素间
  • 最小可觉察误差(JND)与图像压缩

    1 JND算法背景 意义 1算法的概述 最小可觉察误差 JND Just Noticeable Distortion 用于表示人眼不能察觉的最大图像失真 xff0c 体现了人眼对图像改变的容忍度 在图像处理领域 xff0c JND 可以用来
  • 彩色图像压缩(颜色量化)

    调色板色度压缩 调色板的概述 大多数彩色图像采集设备及图像处理软件都采用 24 位真彩色图像数据结构记录图像信息 xff0c 即以红 xff08 R xff09 绿 xff08 G xff09 蓝 xff08 B xff09 彩色模式记录色
  • SSD论文解读

    1 论文简述 题目 SSD Single Shot MultiBox Detector 作者 Wei Liu Dragomir Anguelov Dumitru Erhan Christian Szegedy Scott Reed Chen
  • FaceNet项目实践

    一 论文的原理与复现 1 论文复现 Database xff1a LFW db xff08 论文采用 xff0c rgb图算是较大的典型数据集 xff09 LFW数据库 总共有 13233 张 JPEG 格式图片 xff0c 属于 5749
  • 深度学习领域最新的技术(CV、NLP)

    人工智能正在日益渗透到所有的技术领域 而深度学习 DL 是目前最活跃的分支 最近几年 xff0c DL取得了许多重要进展 其中一些因为事件跟大众关系密切而引人瞩目 xff0c 而有的虽然低调但意义重大 深度学习从业人员应该保持足够的嗅觉 x
  • Linux文件名称乱码GBK转UTF8重命名

    因Java项目运行于Linux之上 xff0c 系统字符集为en US UTF 8 xff08 可通过locale命令查看 xff09 xff0c 而项目Java启动参数设置字符集为GBK 通过上传功能上传的附件文件名称为GBK格式 xff
  • Linux系列讲解 —— 对装有Ubuntu18.04系统的硬盘进行数据迁移

    目录 0 背景介绍1 参照旧硬盘的分区对新硬盘进行分区2 将旧硬盘分区数据迁移到新硬盘对应分区中补充 xff1a 1 新硬盘启动有问题时的对策 需要启动U盘 0 背景介绍 现有一块装有ubuntu系统的旧硬盘 sda xff0c 电脑主机的
  • GPG错误:没有公钥无法验证签名的解决办法

    GPG错误 xff1a 没有公钥无法验证签名的解决办法 sudo apt key adv keyserver hkp keyserver ubuntu com 80 recv 后面加入密钥 xff0c 就能导入公钥了
  • linux中添加环境变量

    linux中添加环境变量 一般是在 etc profile中添加环境 xff0c 但是建议在 etc profile d 下创建一个以 sh结尾的文件 vim etc profil d my env sh 输入i进入编辑模式 eg xff1
  • snprintf函数用法及与sprintf比较

    int snprintf char restrict buf size t n const char restrict format 函数说明 最多从源串中拷贝n xff0d 1个字符到目标串中 xff0c 然后再在后面加一个0 所以如果目