椋鸟C语言笔记#35:scanf/fscanf/sscanf、printf/fprintf/sprintf/snprintf,以及它们的边界检查函数

发布时间:2024年01月20日

萌新的学习笔记,写错了恳请斧正。


目录

scanf/fscanf/sscanf

sscanf使用实例

printf/fprintf/sprintf/snprintf

sprintf使用实例

snprintf使用实例

scanf系列的边界检查函数(scanf_s/fscanf_s/sscanf_s)

printf系列的边界检查函数(printf_s/fprintf_s/sprintf_s/snprintf_s)

利用snprintf决定包含输出的缓冲区大小


scanf/fscanf/sscanf

#include <stdio.h>
int scanf( const char* format, ... );
int fscanf( FILE* stream, const char* format, ... );
int sscanf( const char* buffer, const char* format, ... );

这三个函数从各种资源读取数据,按照格式控制字符串(format)转译,并将结果存储到指定位置

这三个函数返回成功赋值的接受参数的数量(可以为零),出错则返回EOF。

他们的区别在于:

  • scanf从stdin读取数据。
  • fscanf从文件流stream读取数据。
  • sscanf从以'\0'结尾的字符串buffer中读取数据? 。

scanf和fscanf已经具体的讲过了

sscanf的特性与fscanf等同,抵达字符串尾('\0')等价于fscanf抵达文件尾的条件

sscanf使用实例
#include <stdio.h>
#include <stdlib.h>

int main()
{
	int a = 0;
	char* arr = (int*)malloc(100 * sizeof(int));
	char str[100] = { 0 };
	sscanf(" 114514 afjoiweaj jiojl", "%d%s%s", &a, arr, str);
	printf("%d - %s - %s", a, arr, str);
	return 0;
}

printf/fprintf/sprintf/snprintf

#include <stdio.h>
int printf( const char* format, ... );
int fprintf( FILE* stream, const char* format, ... );
int sprintf( char* buffer, const char* format, ... );
int snprintf( char* buffer, int bufsz, const char* format, ... );

前三个函数从给定位置加载数据,按一定要求转换为字符串等价物,并写结果到各种地方

第四个函数snprintf就是增加了长度限制的sprintf,其他没有区别

对比四个函数:

  • printf直接写结果到stdout。返回打印的字符数,出错返回一个负值。
  • fprintf写结果到流stream。返回写入的字符数,出错返回一个负值。
  • sprintf写结果到字符串buffer,结尾补上'\0'。返回写入的字符数(不包括空终止符'\0'),出错返回一个负值。
  • snprintf写结果到字符串buffer,但是至多写bufsz-1个字符,结尾再加'\0'。返回忽略bufsz限制情况下本应写入的字符数,出错返回一个负值。如果bufsz为0,则不写入任何内容;如果buffer是空指针,正常计算返回值但是不会真的写入内容。
sprintf使用实例
#include <stdio.h>

int main()
{
	char a[10] = "1919810";
	sprintf(a, "%s", "114514");
	printf(a);

	return 0;
}
snprintf使用实例
#include <stdio.h>

int main()
{
	char str[100];
	snprintf(str, 100, "Hello %s", "World!");
	printf(str);
	return 0;
}

scanf系列的边界检查函数(scanf_s/fscanf_s/sscanf_s)

首先声明一点,这些为了安全创造的带有下划线s结尾的边界检查函数是在C11标准中定义的可选函数,所以并不是每一个编译器都能编译这个函数,只有在使用的编译器实现定义“__STDC_LIB_EXT1__”而且在包含<stdio.h>前定义“__STDC_WANT_LIB_EXT1__”为整数常量1时才能保证这些函数一定可以使用。当然,不少编译器都默认开启了这些函数,比如在Visual Studio中这些函数就是默认开启的,甚至如果使用不安全的不带边界检查的这些函数还会警告并中断编译(在文件前定义“_CRT_SECURE_NO_WARNINGS”为整数常量1可以忽略不安全函数警告)

//就是加上这一行以禁用不安全函数警告
#define _CRT_SECURE_NO_WARNINGS 1
//后面的1可以省略,默认定义值为1

这些函数相比于传统函数,多了边界检查,可以防止缓冲区溢出

#include <stdio.h>
int scanf_s(const char* format, ...);
int fscanf_s(FILE* stream, const char* format, ...);
int sscanf_s(const char* buffer, const char* format, ...);

机制与传统函数相同,除了:

1.?如果在格式限定中使用了%c 、?%s 或?%[,后面可变参数列表中与其对应的参数变成两个,第一个仍为传统函数的参数,第二个则是类型为rsize_t的一个值,用于限定这个格式匹配符最多能匹配多少个字节的内容。比方说:

#include <stdio.h>

int main()
{
	int n = 0, m = 0;
	char arr[10] = { 0 };
	scanf_s("%d%x%s", &n, &m, arr, (rsize_t)sizeof arr);
	printf("n=%d m=%d arr=%s\n", n, m, arr);
	return 0;
}

这段代码中,scanf_s中%s对应的参数包括arr和(rsize_t)sizeof arr两个arr是读取后保存的位置,后面则是限定最多读取10个字节(空终止符'\0'是算在内的,所以实际最多读9个)

2.?在运行时额外检测以下违规行为,如果出现违规也返回EOF:

  • 任何指针类型的参数是空指针
  • format、stream、buffer中存在空指针
  • 使用%c 、?%s 或?%[ 会写入的字符数,加上空终止字符'\0',要超过我们通过参数给定的限制
  • 可选的其他错误,因编译器而异

printf系列的边界检查函数(printf_s/fprintf_s/sprintf_s/snprintf_s)

其使用条件与优越性与scanf系列的边界检查函数相同

#include <stdio.h>
int printf_s(const char* format, ...);
int fprintf_s(FILE* stream, const char* format, ...);
int sprintf_s(char* buffer, rsize_t bufsz, const char* format, ...);
int snprintf_s(char* buffer, rsize_t bufsz, const char* format, ...);

其机制与传统函数相同,除了在运行时检测如下违规行为:

  • format中存在转换说明符%n
  • 任何对应%s的参数是空指针
  • format或buffer是空指针
  • bufsz为0或大于其类型限制(RSIZE_MAX)
  • 出现编码错误
  • (仅sprintf_s)buffer字符串长度(包括'\0')超过bufsz

关于返回值:

  • printf_s与fprintf_s返回传输到输出流的字符数,若出现输出错误、运行时制约违规错误或编码错误则为一个负值。
  • sprintf_s返回写入到buffer的字符数,不计空终止字符,若在运行时违规返回零,若编码错误返回负值。
  • snprintf_s返回返回忽略bufsz限制情况下本应写入的字符数,不计空终止字符,若出现输出错误、运行时违规或编码错误则返回负值。

利用snprintf决定包含输出的缓冲区大小

当我们不确定输出字符串的大小时,我们可以利用以零为bufsz和以空指针为buffer的snprintf来决定缓冲区大小。为什么呢?

当bufsz为零、buffer为空指针时,根据上面的介绍,我们知道这只会输出忽略bufsz限制情况下本应写入的字符数,而且不会产生其他影响。而这个输出值,不就是我们需要的缓冲区大小值吗?

下面是一个例子:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

int main()
{
	const char* format = "sqrt(2) = %f";
	int sz = snprintf(NULL, 0, format, sqrt(2));
	char* buf = (char*)malloc(sz + 1);	// +1 for '\0'
	snprintf(buf, sz + 1, format, sqrt(2));
	printf("%s\n", buf);

	return 0;
}

文章来源:https://blog.csdn.net/StarlingLin/article/details/135639000
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。