scnprintf()和snprintf()、vscnprintf()和vsnprintf()

2023-05-16

写过Linux驱动或者内核态程序的人应该都知道,编译时会有这样一个警告:

use scnprintf() instead of snprintf()

为什么在编译驱动或者内核态程序的时候会有这个警告呢?

据说因为snprintf()有很大内存越界的风险?在内核态,内存越界往往是灾难性的后果

下面是从Linux内核源码中复制的一段snprintf()和scnprintf()的实现(内核版本:4.20)

/**
 * snprintf - Format a string and place it in a buffer
 * @buf: The buffer to place the result into
 * @size: The size of the buffer, including the trailing null space
 * @fmt: The format string to use
 * @...: Arguments for the format string
 *
 * The return value is the number of characters which would be
 * generated for the given input, excluding the trailing null,
 * as per ISO C99.  If the return is greater than or equal to
 * @size, the resulting string is truncated.
 *
 * See the vsnprintf() documentation for format string extensions over C99.
 */
int snprintf(char *buf, size_t size, const char *fmt, ...)
{
	va_list args;
	int i;

	va_start(args, fmt);
	i = vsnprintf(buf, size, fmt, args);/*这是唯一的区别*/
	va_end(args);

	return i;
}
/**
 * scnprintf - Format a string and place it in a buffer
 * @buf: The buffer to place the result into
 * @size: The size of the buffer, including the trailing null space
 * @fmt: The format string to use
 * @...: Arguments for the format string
 *
 * The return value is the number of characters written into @buf not including
 * the trailing '\0'. If @size is == 0 the function returns 0.
 */

int scnprintf(char *buf, size_t size, const char *fmt, ...)
{
	va_list args;
	int i;

	va_start(args, fmt);
	i = vscnprintf(buf, size, fmt, args);/*这是唯一的区别*/
	va_end(args);

	return i;
}

可以发现,两者返回值是不一样的,一个返回的是将要被写入到buf的字符数,一个返回的是已经被写入到buf的字符数。snprintf()是通过vsnprintf()实现的,而scnprintf()是通过vscnprintf()实现的,所以我们还得去看vsnprintf()和vscnprintf(),下面是vscnprintf()的声明:

#include <stdarg.h>

int vprintf(const char *format, va_list ap);

int vfprintf(FILE *stream, const char *format, va_list ap);
int vdprintf(int fd, const char *format, va_list ap);
int vsprintf(char *str, const char *format, va_list ap);
int vsnprintf(char *str, size_t size, const char *format, va_list ap);

下面是实现部分:

/**
 * vsnprintf - Format a string and place it in a buffer
 * @buf: The buffer to place the result into
 * @size: The size of the buffer, including the trailing null space
 * @fmt: The format string to use
 * @args: Arguments for the format string
 *
 * This function generally follows C99 vsnprintf, but has some
 * extensions and a few limitations:
 *
 *  - ``%n`` is unsupported
 *  - ``%p*`` is handled by pointer()
 *
 * See pointer() or Documentation/core-api/printk-formats.rst for more
 * extensive description.
 *
 * **Please update the documentation in both places when making changes**
 *
 * The return value is the number of characters which would
 * be generated for the given input, excluding the trailing
 * '\0', as per ISO C99. If you want to have the exact
 * number of characters written into @buf as return value
 * (not including the trailing '\0'), use vscnprintf(). If the
 * return is greater than or equal to @size, the resulting
 * string is truncated.
 *
 * If you're not already dealing with a va_list consider using snprintf().
 */
int vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
{
	unsigned long long num;
	char *str, *end;
	struct printf_spec spec = {0};

	/* Reject out-of-range values early.  Large positive sizes are
	   used for unknown buffer sizes. */
	if (WARN_ON_ONCE(size > INT_MAX))
		return 0;

	str = buf;
	end = buf + size;

	/* Make sure end is always >= buf */
	if (end < buf) {
		end = ((void *)-1);
		size = end - buf;
	}

	while (*fmt) {
		const char *old_fmt = fmt;
		int read = format_decode(fmt, &spec);

		fmt += read;

		switch (spec.type) {
		case FORMAT_TYPE_NONE: {
			int copy = read;
			if (str < end) {/*这里已经保证了不会越界*/
				if (copy > end - str)
					copy = end - str;
				memcpy(str, old_fmt, copy);
			}
			str += read;
			break;
		}

		case FORMAT_TYPE_WIDTH:
			set_field_width(&spec, va_arg(args, int));
			break;

		case FORMAT_TYPE_PRECISION:
			set_precision(&spec, va_arg(args, int));
			break;

		case FORMAT_TYPE_CHAR: {
			char c;

			if (!(spec.flags & LEFT)) {
				while (--spec.field_width > 0) {
					if (str < end)
						*str = ' ';
					++str;

				}
			}
			c = (unsigned char) va_arg(args, int);
			if (str < end)
				*str = c;
			++str;
			while (--spec.field_width > 0) {
				if (str < end)
					*str = ' ';
				++str;
			}
			break;
		}

		case FORMAT_TYPE_STR:
			str = string(str, end, va_arg(args, char *), spec);
			break;

		case FORMAT_TYPE_PTR:
			str = pointer(fmt, str, end, va_arg(args, void *),
				      spec);
			while (isalnum(*fmt))
				fmt++;
			break;

		case FORMAT_TYPE_PERCENT_CHAR:
			if (str < end)
				*str = '%';
			++str;
			break;

		case FORMAT_TYPE_INVALID:
			/*
			 * Presumably the arguments passed gcc's type
			 * checking, but there is no safe or sane way
			 * for us to continue parsing the format and
			 * fetching from the va_list; the remaining
			 * specifiers and arguments would be out of
			 * sync.
			 */
			goto out;

		default:
			switch (spec.type) {
			case FORMAT_TYPE_LONG_LONG:
				num = va_arg(args, long long);
				break;
			case FORMAT_TYPE_ULONG:
				num = va_arg(args, unsigned long);
				break;
			case FORMAT_TYPE_LONG:
				num = va_arg(args, long);
				break;
			case FORMAT_TYPE_SIZE_T:
				if (spec.flags & SIGN)
					num = va_arg(args, ssize_t);
				else
					num = va_arg(args, size_t);
				break;
			case FORMAT_TYPE_PTRDIFF:
				num = va_arg(args, ptrdiff_t);
				break;
			case FORMAT_TYPE_UBYTE:
				num = (unsigned char) va_arg(args, int);
				break;
			case FORMAT_TYPE_BYTE:
				num = (signed char) va_arg(args, int);
				break;
			case FORMAT_TYPE_USHORT:
				num = (unsigned short) va_arg(args, int);
				break;
			case FORMAT_TYPE_SHORT:
				num = (short) va_arg(args, int);
				break;
			case FORMAT_TYPE_INT:
				num = (int) va_arg(args, int);
				break;
			default:
				num = va_arg(args, unsigned int);
			}

			str = number(str, end, num, spec);
		}
	}

out:
	if (size > 0) {
		if (str < end)
			*str = '\0';
		else
			end[-1] = '\0';
	}

	/* the trailing null byte doesn't count towards the total */
	return str-buf;

}
EXPORT_SYMBOL(vsnprintf);

/**
 * vscnprintf - Format a string and place it in a buffer
 * @buf: The buffer to place the result into
 * @size: The size of the buffer, including the trailing null space
 * @fmt: The format string to use
 * @args: Arguments for the format string
 *
 * The return value is the number of characters which have been written into
 * the @buf not including the trailing '\0'. If @size is == 0 the function
 * returns 0.
 *
 * If you're not already dealing with a va_list consider using scnprintf().
 *
 * See the vsnprintf() documentation for format string extensions over C99.
 */
int vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
{
	int i;

	i = vsnprintf(buf, size, fmt, args);

	if (likely(i < size))
		return i;
	if (size != 0)
		return size - 1;
	return 0;
}

从上面的代码可以看出,scnprintf()和snprintf()、vscnprintf()和vsnprintf()都不会越界,而且都会在最后面加一个结束符'\0',返回值的大小都不包含最后的结束符,都会截断,不同之处是:

scnprintf()和vscnprintf()返回的是写入到buf的字符数,而snprintf()和vsnprintf()返回的是格式化之后得到字符的长度(可能比size要大)

下面是一段验证的代码:

#include <stdarg.h>
#include <stdio.h>
#include <string.h>

int vscnprintf(char *buf, size_t size, const char *fmt, va_list args);
int make_cnprintf(const char *fmt,...);
int make_nprintf(const char *fmt,...);

int main()
{
	make_cnprintf("%s","123456789ABC");
	make_cnprintf("%s","123456789A");
	make_cnprintf("%s","123456");
	
	make_nprintf("%s","123456789ABC");
	make_nprintf("%s","123456789A");
	make_nprintf("%s","123456");

	return 0;
}

int vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
{
	int i;

	i = vsnprintf(buf, size, fmt, args);

	if (i < size)
		return i;
	if (size != 0)
		return size - 1;
	return 0;
}

int make_cnprintf(const char *fmt,...)
{
	int cx = 0;

	char buf[10] = {0};
	va_list args;
	va_start(args,fmt);
	cx = vscnprintf(buf,10,fmt,args);
	printf("vscnprintf return cx=%d,buf=%s\n",cx,buf);
	va_end(args);

	return 0;
}

int make_nprintf(const char *fmt,...)
{
	int cx = 0;

	char buf[10] = {0};
	va_list args;
	va_start(args,fmt);
	cx = vsnprintf(buf,10,"%s",args);
	printf("vscprintf return cx=%d,buf=%s\n",cx,buf);
	va_end(args);

	return 0;
}

运行结果:

vscnprintf return cx=9,buf=123456789
vscnprintf return cx=9,buf=123456789
vscnprintf return cx=6,buf=123456
vscprintf return cx=12,buf=123456789
vscprintf return cx=10,buf=123456789
vscprintf return cx=6,buf=123456

参考文档:

https://elixir.bootlin.com/linux/latest/ident/scnprintf

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

scnprintf()和snprintf()、vscnprintf()和vsnprintf() 的相关文章

  • Unable to perform this action because the process is running.

    总感觉会有小伙伴和我一样纠结吧 我在使用vs code的时候想用调试控制台输入输出 xff0c 结果在输入的时候遇到了这个问题 xff0c 为什么呢 xff0c 我也不知道 xff0c 但是有一个是明确的 xff0c 就是调试控制台不可以作
  • VS Code 翻译插件

    translate var 使用翻译api将其他语言到英文 转换成常见的变量命名形式 Automatically translate words into English variable name Keybindings win 34 A
  • 从ASP.NET Core 3.1中的当前URL获取主机信息

    目录 介绍 问题陈述 解决方案 介绍 在处理 Web 应用程序时 xff0c 很自然 xff0c 我们需要在产品生命周期的各个阶段在各种环境 xff08 即开发 xff0c 测试 xff0c 生产等 xff09 之间切换 换句话说 xff0
  • ONNX系列六 --- 在Java中使用可移植的ONNX AI模型

    目录 安装和导入ONNX运行时 载入ONNX模型 使用ONNX运行时进行预测 摘要和后续步骤 参考文献 下载源547 1 KB 系列文章列表如下 xff1a ONNX系列一 带有ONNX的便携式神经网络 ONNX系列二 使用ONNX使Ker
  • 绒毛动物探测器:通过TensorFlow.js中的迁移学习识别浏览器中的自定义对象

    目录 起点 MobileNet v1体系结构上的迁移学习 修改模型 训练新模式 运行物体识别 终点线 下一步是什么 xff1f 我们可以检测到脸部吗 xff1f 下载TensorFlowJS Examples master zip 6 1
  • YOLOv7 在 ML.NET 中使用 ONNX 检测对象

    目录 什么是 YOLO ONNX 模型 执行预测 示例和参考 References 什么是 YOLO YOLO xff08 You Only Look Once xff09 是一种先进的实时目标检测系统 它是一个在COCO数据集上预训练的物
  • 如何转换PyTorch模型并使用OpenVINO™工具包运行它

    注意 xff1a 本文是使用 OpenVINO 2022 1 创建的 如果您想知道如何使用 OpenVINO 2021 4 的旧 API xff0c 请查看 此notebook 尽管 PyTorch 是 AI 训练的绝佳框架 xff0c 可
  • 程序设计思维与实践 Week14 作业

    A Q老师与石头剪刀布 题意 xff1a 每一个大人曾经都是一个小孩 xff0c Q老师 也一样 为了回忆童年 xff0c Q老师 和 Monika 玩起了石头剪刀布的游戏 xff0c 游戏一共 n 轮 无所不知的 Q老师 知道每一轮 Mo
  • 高可用:MongoDB 容器部署

    MongoDB 是一款 NoSQL 数据 xff0c 通常用来存储非结构化数据 xff0c 我们的产品中也有用到 xff0c 例如 xff1a 一些文件存储在 MongoDB 的 GridFS 中 MongoDB 有三种方式来实现高可用 x
  • 在C#语言中监视SQL Server中的实时数据库更改

    目录 介绍 介绍 在本文中 xff0c 我想向您解释如何在不需要 SQL 依赖和服务代理的情况下监视数据库 有许多方法可以监视数据库更改 xff0c 其中一种方法是 SQL 依赖和服务代理 xff0c 但是在本教程中 xff0c 我们将不使
  • 用Python 3编写的Python 3代码生成器

    目录 介绍 使用代码 关于create python prog py 关于variable name 关于parameter type 关于parameter count token 关于parameter switches 有关布尔参数的
  • 未能加载文件或程序集“Newtonsoft.Json, Version=4.5.0.0"[已解决]

    前两天升级系统架构 xff0c 升级后打开网页报错了 xff01 xff01 xff01 详细信息如下 xff1a 未能加载文件或程序集 Newtonsoft Json Version 61 4 5 0 0 Culture 61 neutr
  • 联想拯救者Y7000系统安装之路(Win10系统)

    最近新购得联想拯救者Y7000 xff0c 到手的第一件事情就是重装系统 xff0c 这个大家都懂的 使用F2进入BIOS界面 xff0c 使用F12可进入快速启动选择U盘启动 接下来问题来了 xff0c 我用老毛桃制作的U盘启动盘 xff

随机推荐

  • win10 服务主机:DCOM服务器进程启动器 进程导致电脑卡死解决思路

    新买的笔记本 xff1a 联想拯救者Y7000 系统 xff1a win10专业版 xff08 已经禁用了网上可搜的服务 xff0c 没有win10开始菜单的磁条 xff09 原因 xff1a 总是在开机一段时间后系统卡死 xff0c 只能
  • Windows 全新终端 Windows Terminal

    本项目包含 xff1a Windows TerminalWindows 控制台主机 conhost exe 上述两项目的共享组件ColorTool示例项目 将展示如何使用 Windows Console APIs Windows Termi
  • python项目打包发布详解

    PyInstaller打包Python项目详解 lt h1 gt lt div class 61 34 clear 34 gt lt div gt lt div class 61 34 postBody 34 gt PyInstaller打
  • python批处理打开多个文件

    背景 xff1a 有时候我们需要在服务器上同时运行多个程式 xff0c 但是却需要一个一个的打开 xff0c 比较费时间 xff0c 而且一旦服务器重启后 xff0c 不懂程式运行的人受限于环境及代码原理 xff0c 很难逐个将程式逐个打开
  • 挂载别的系统挂掉的磁盘解决步骤,mount: unknown filesystem type ‘LVM2_member‘ 报错

    挂载别的系统挂掉的磁盘解决步骤 1 在新的虚机添加磁盘 按照下边操作步骤即可使linux系统重新读取并识别到新硬盘 xff1a 1 1 确定主机总线号 xff1a root 64 iNeedle ls sys class scsi host
  • 序设计思维与实践 CSP-M4

    A 题意 xff1a 题目描述 这一天 xff0c TT因为疫情在家憋得难受 xff0c 在云吸猫一小时后 xff0c TT决定去附近自家的山头游玩 TT来到一个小湖边 xff0c 看到了许多在湖边嬉戏的鸭子 xff0c TT顿生羡慕 此时
  • C++ 构造函数详解

    目录 0 什么是构造函数 1 默认构造函数 2 一般构造函数 3 拷贝构造函数 4 转换构造函数 5 移动构造函数 0 什么是构造函数 在定义类的成员函数的时候 xff0c 一般的成员函数与普通的全局函数没有太大的区别 xff0c 在定义函
  • Copilot 简单测评

    年初的时候通过了Copilot的试用申请资格 xff0c 试用到现在也几个月了 xff0c 说一下使用的感受 最开始理解Copilot xff0c 是通过注释来生成代码 xff0c 在这个想法下 xff0c 感觉自己又又又又要失业了 xff
  • iPhone开发【十四】多线程开发之NSThread——子线程模拟耗时操作

    转载请注明出处 xff0c 原文网址 xff1a http blog csdn net m changgong article details 8213964 作者 xff1a 张燕广 实现的功能 xff1a 1 xff09 演示多线程开发
  • 学习c语言的总结

    学习时间 xff1a 早上9点 晚上9点 学习内容 xff1a 利用c语言对 的代码学习 xff0c 并根据自己的理解编写代码 xff0c 最后整合学习的代码和自己理解的代码 xff0c 编写出更优的代码 学习体会 xff1a 对一个问题举
  • Makefile使用细节

    变量及通配符 A 61 C 即时变量 xff0c 此时C未定义 xff0c A为空 B 61 C 延时变量 xff0c 用到B时再确定具体的值 C 61 abc C 61 123 C不是第一次定义 xff0c 被忽略 C 43 61 789
  • Debian修改桌面系统

    今天 xff0c 装了Debian xff0c 发现其桌面不好看 xff0c 感觉就像瘟逗死系统 xff0c 寻思着更换一下 xff0c 于是就度娘 xff0c 但遗憾的是没有找到 xff0c 想一下不如自个儿研究哈哈 xff0c 所以记录
  • csp 序列查询新解 解决70分超时和错误的思路

    这个题的代码我再也不会看的 因为这题就是一个发现数与数之间联系的一道题 xff0c 不会再看了 总结一下 xff1a 这道题别人AC是用了两层for循环 但内层的for里第三个条件不是i 43 43 是i 43 61 一个区间长度 这就过了
  • bat批处理文件建立和打开

    一 新建bat文件 1 1新建文本文件 xff0c 在文本文件写入如下内容 xff1a xff08 注意不要有中文路径和中文名称 xff09 格式 xff1a start 目录路径 app exe 具体的实例如下所示 xff1a start
  • 安装树莓派vnc或者xrdp出错解决办法(树莓派ping不同Windows、但是Windows可以ping通树莓派)(树莓派上搭建好了vnc环境和xrdp环境)

    1 1出现问题 博主按照这篇教程https blog csdn net naibozhuan3744 article details 84961041搭建树莓派vnc环境或者xrdp环境时 xff0c 一直出现win10系统ping不通树莓
  • C++可变参数模板函数基本用法

    可变参数模板可以创建任意个参数的模板函数和模板类 xff0c 本文主要介绍可变参数模板函数 1 1可变参数模板函数声明和定义 template lt typename Args gt Args是一个模板参数包 void Show Args
  • python+KLT光流法匹配

    span class token comment 光立法匹配 span span class token function import span numpy as np span class token function import s
  • python3 最长公共前缀

    给定一个大小为 n 的字符串数组 strs xff0c 其中包含n个字符串 编写一个函数来查找字符串数组中的最长公共前缀 xff0c 返回这个公共前缀 import random import re class Solution def l
  • Android Studio 设置阿里云镜像代理(如果设置之后还是远程仓库下载失败,请仔细阅读其内容就可以解决了)

    1 在project的build gradle文件的repositories标签和allprojects标签的repositories标签下加入以下阿里云镜像代理 xff08 如下图一和图二 xff0c 记得要把阿里云镜像代理放在repos
  • scnprintf()和snprintf()、vscnprintf()和vsnprintf()

    写过Linux驱动或者内核态程序的人应该都知道 xff0c 编译时会有这样一个警告 xff1a use scnprintf instead of snprintf 为什么在编译驱动或者内核态程序的时候会有这个警告呢 xff1f 据说因为sn