在weibo上看到Laruence大神修复了一个使用snprintf的bug (http://t.cn/Rm6AuFh) 引起了TK教主的关注。TK教主着重提到了在windows下snprintf与_snprintf的行为有差别。

想想自己之前也在windows下写过代码,因具体的使用场景没有触发这种差异,因而对此也没有特别留意。下面对此写代码和查MSDN了具体验证了差别,结果记录如下。

先说snprintf,相信只要写过C代码的程序员,肯定用过这个C库函数,其声明如下

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

其向 str 为起始地址,长度为 size 的buffer中,按 format 指定的格式进行格式化写入串。size 限制了最大向 str 写入的字节数,但具体根据 format 格式和给定的额外参数,实际拼接出的串长度会超过 size。此时的结果就需要特别注意。man中对snprintf的说明如下

Upon successful return, these functions return the number of characters printed (excluding the null byte used to end output to strings).

The functions snprintf() and vsnprintf() do not write more than size bytes (including the terminating null byte ('\0')). If the output was truncated due to

this limit, then the return value is the number of characters (excluding the terminating null byte) which would have been written to the final string if

enough space had been available. Thus, a return value of size or more means that the output was truncated. (See also below under NOTES.)

返回值是要特别注意的,并不是是向 str 写入的字节数,而是 format 和对应实参拼接出的串长度(称为len, 不包含尾部0字节)。

当 len 小于 size 时,除了将该串写入buffer之外,还额外追加一个0字节。

当 len 等于 size 时,将该串写入buffer之后,会将最后字节清零,该串位内容未字节也就被截断了。

当 len 大于 size 时,最多写入该串前(size-1)个字节,并将第 size 个字节清零。

snprintf 在 linux 下(libc-2.23.so) 和 windows 下(VS2015 VCRUNTIME140)行为一致,都是如上所述。

但在windows下 _snprintf 的行为和snprintf不完全一致。

当 len 小于 size 时,除了将该串写入buffer之外,还额外追加一个0字节,和 snprintf 相同

当 len 等于 size 时,直接将串写入buffer,并不会对尾字节清零,返回值为 len,和 snprintf 不同

当 len 大于 size 时,直接将串写入buffer,并不将第 size 个字节清零,返回值为 -1,和 snprintf 不同

MSDN对应的说明如下

Let len be the length of the formatted data string, not including the terminating null. len and count are in bytes for _snprintf, wide characters for _snwprintf.

If len < count, len characters are stored in buffer, a null-terminator is appended, and len is returned.

If len = count, len characters are stored in buffer, no null-terminator is appended, and len is returned.

If len > count, count characters are stored in buffer, no null-terminator is appended, and a negative value is returned.

实际使用如下代码进行测试,结果如下,注释中是 GDB 查看的中间结果。

#include <stdio.h>
#include <string.h> #ifdef WIN32
#define snprintf _snprintf
#endif int main(int argc, char *argv[])
{
char buffer[16];
int ret; /*
(gdb) p ret
$1 = 4
(gdb) x /16bx buffer
0x7ffffffde250: 0x31 0x32 0x33 0x34 0x00 0x01 0x01 0x01
0x7ffffffde258: 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01
*/
memset(buffer, 1, sizeof(buffer));
ret = snprintf(buffer, 8, "%s", "1234"); /*
(gdb) p ret
$2 = 8
(gdb) x /16bx buffer
0x7ffffffde250: 0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x00
0x7ffffffde258: 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01
*/
memset(buffer, 1, sizeof(buffer));
ret = snprintf(buffer, 8, "%s", "12345678"); /*
(gdb) p ret
$3 = 9
(gdb) x /16bx buffer
0x7ffffffde250: 0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x00
0x7ffffffde258: 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01
*/
memset(buffer, 1, sizeof(buffer));
ret = snprintf(buffer, 8, "%s", "123456789"); return 0;
}

而在VS下,查看的中间结果, snprintf 和 _snprintf 分别如下

snprintf

ret = 4

buffer: 31 32 33 34 00, 01 01 01 01 01 01 01 01 01 01 01

ret = 8

buffer: 31 32 33 34 35 36 37 00, 01 01 01 01 01 01 01 01

ret = 9

buffer: 31 32 33 34 35 36 37 00, 01 01 01 01 01 01 01 01

_snprintf

ret = 4

buffer: 31 32 33 34 00, 01 01 01 01 01 01 01 01 01 01 01

ret = 8

buffer: 31 32 33 34 35 36 37 38, 01 01 01 01 01 01 01 01

ret = -1

buffer: 31 32 33 34 35 36 37 38, 01 01 01 01 01 01 01 01

snprintf笔记的更多相关文章

  1. 《Linux/Unix系统编程手册》读书笔记7 (/proc文件的简介和运用)

    <Linux/Unix系统编程手册>读书笔记 目录 第11章 这章主要讲了关于Linux和UNIX的系统资源的限制. 关于限制都存在一个最小值,这些最小值为<limits.h> ...

  2. sc7731 Android 5.1 LCD驱动简明笔记之三

    此篇笔记基于sc7731 - android 5.1,对lcd的gralloc库做一个简明笔记. 第一部分 调用gralloc.sc8830.so所谓的Gralloc模块,它就是一个模块,一个操作ke ...

  3. 错误内存【读书笔记】C程序中常见的内存操作有关的典型编程错误

    题记:写这篇博客要主是加深自己对错误内存的认识和总结实现算法时的一些验经和训教,如果有错误请指出,万分感谢. 对C/C++程序员来讲,内存管理是个不小的挑战,绝对值得慎之又慎,否则让由上万行代码构成的 ...

  4. Linux进程间通信IPC学习笔记之管道

    基础知识: 管道是最初的Unix IPC形式,可追溯到1973年的Unix第3版.使用其应注意两点: 1)没有名字: 2)用于共同祖先间的进程通信: 3)读写操作用read和write函数 #incl ...

  5. redis 学习笔记一

    找了半天,发觉还是redis的源码看起来比较舒服.所以决定今年把redis的源码读一遍顺便做个读书笔记.好好记录下.话说现在越来不越不愿意用脑袋来记录东西,喜欢靠note来记.话说这样不爱用脑会不会过 ...

  6. 用gdb调试程序笔记: 以段错误(Segmental fault)为例

    用gdb调试程序笔记: 以段错误(Segmental fault)为例[转] 1.背景介绍2.程序中常见的bug分类3.程序调试器(如gdb)有什么用4.段错误(Segmental fault)介绍5 ...

  7. 树莓派学习笔记——使用文件IO操作GPIO SysFs方式

    0 前言     本文描写叙述假设通过文件IO sysfs方式控制树莓派 GPIO端口.通过sysfs方式控制GPIO,先訪问/sys/class/gpio文件夹,向export文件写入GPIO编号, ...

  8. 【unix网络编程第三版】阅读笔记(二):套接字编程简介

    unp第二章主要将了TCP和UDP的简介,这些在<TCP/IP详解>和<计算机网络>等书中有很多细致的讲解,可以参考本人的这篇博客[计算机网络 第五版]阅读笔记之五:运输层,这 ...

  9. Muduo学习笔记(一) 什么都不做的EventLoop

    Muduo学习笔记(一) 什么都不做的EventLoop EventLoop EventLoop的基本接口包括构造.析构.loop(). One Loop Per Thread 一个线程只有一个Eve ...

随机推荐

  1. 转载:dos批处理中路径获取

    在DOS的批处理中,有时候需要知道当前的路径. 在DOS中,有两个环境变量可以跟当前路径有关,一个是%cd%, 一个是%~dp0.      这两个变量的用法和代表的内容是不同的.  1. %cd% ...

  2. linux 7.2安装扩展redis

    unzip phpredis-php7.zip cd phpredis-php7 /usr/local/php7./bin/phpize ./configure --with-php-config=/ ...

  3. Linux c codeblock的使用(三):使用函数库

    (一)概念 什么是函数库呢?一下子说概念大家可能不太熟悉,但是这实际上是大家在windows系统上经常见到的东西.没错,就是那些后缀为DLL的文件. linux上实际也有自己的函数库文件,文件类型为. ...

  4. 刷seed有感

    今天又把seed刷了一遍 昨天去了基佬他们公司.第一次去他们公司.米虫科技,在重庆算是一家中型公司吧. 他去公司加班写一个游戏的封面,第一次感觉ui的不给设计图真的很坑.一个页面所有东西 自己凭感觉写 ...

  5. vue中几种常见技巧

    1.校验延迟:点击进入页面不希望校验必填字段this.$nextTick(() => { this.$refs.formRules.clearValidate() })2.$on $emit $ ...

  6. 【转载】DQL、DML、DDL、DCL的概念与区别

    原文地址:https://www.cnblogs.com/fan-yuan/p/7879353.html SQL(Structure Query Language)语言是数据库的核心语言. SQL的发 ...

  7. Linux 设备驱动之字符设备

    参考转载博客:http://blog.chinaunix.net/uid-26833883-id-4369060.html https://www.cnblogs.com/xiaojiang1025/ ...

  8. java算法02 - 树

    树是一类重要的非线性结构.而二叉树是一种比较重要的树,接下来我们来了解二叉树的相关内容. 二叉搜索树:每个节点都不比它左子树的任意元素小,而且不比它的右子树的任意元素大. /** * 二叉搜索树 O( ...

  9. python中对文件、文件夹,目录的基本操作

    一.python中对文件.文件夹操作时经常用到的os模块和shutil模块常用方法.1.得到当前工作目录,即当前Python脚本工作的目录路径: os.getcwd()2.返回指定目录下的所有文件和目 ...

  10. js datagrid 移动去重

    function dataLeft(){ var checkedData = $(listTemplate_right).datagrid('getChecked'); var rows = $(li ...