C 格式说明符的意义何在?

What is the point of format specifier in C?

提问人:jeaq 提问时间:1/24/2023 最后编辑:Jabberwockyjeaq 更新时间:1/25/2023 访问量:223

问:

如果我们在 printf 之前设置了变量的类型,那么 C 中格式说明符的意义何在?

例如:

#include<stdio.h>
int main(void)
{
    int a=7
    printf("%d", a);
} 

就像,它已经准备好了,它是什么,它是整数(int)。那么,添加 %d 来指定它是一个整数有什么意义呢?

C 整数 格式说明符

评论

3赞 Martin 1/24/2023
如何知道您传递给函数的类型?printf
4赞 Tom Karzes 1/24/2023
下面是一个练习,可以让你回答自己的问题:编写你自己的版本,它不需要格式字符串,但它可以处理任意数量的 、 或 类型的参数。由于我刚才给出的原因,您将无法做到这一点。printfintdoublechar *
1赞 Tom Karzes 1/24/2023
@jeaq对调用方中的局部变量一无所知。它所知道的只是第二个参数是 (如格式字符串所示),在提取该参数后,它知道它具有值 。这就是它所知道的。没有发生量子纠缠。试着理解这只是一个被调用的函数。编译器不会为每个调用自定义构建它的不同版本。只有一个。printfaint7printf
1赞 Steve Summit 1/24/2023
我认为这其实是一个很好的问题。如果你不了解 C 语言的古老历史,或者你习惯了任何数量的带有内置语句的语言,那么 C 函数具有它所做的奇怪要求是很奇怪的,如果你没有意识到它实际上是一个普通的函数,那就更奇怪了。printprintfprintf
2赞 Steve Summit 1/24/2023
@Lundin 这是你的意见,你有权这样做,但我是一个专业的 C 和 C++ 程序员,几乎每天都在使用。Chacun à son goût.printf

答:

1赞 Allan Wind 1/24/2023 #1

printf()接受可变数量的参数。要处理这些变量参数,它 () 需要知道最后一个固定参数是。它 () 还需要知道每个参数的类型,以便计算要读取的数据量。va_start()va_arg()

格式说明符也是一个紧凑的模板(或 DSL),用于表示文本和变量的格式设置方式,包括字段宽度、对齐方式、精度、编码。

评论

0赞 jeaq 1/24/2023
谢谢。我发布这个问题的原因是,printf 知道 a=7 但不知道 a 是 int,这对我来说很奇怪。这两者都在上一行中说明过。也许更好的问题是,它怎么知道 a=7?
1赞 Jonathan Leffler 1/24/2023
@jeaq — 它知道,因为您在格式字符串中指定了第二个参数的类型是 ,所以它从变量参数列表中提取一个参数并设置格式,而该值恰好是 7。事先,它不知道值是 7;当它看起来时,它发现它是 7。大致喜欢事先不知道值是;它从堆栈中弹出值并使用它进行计算。如果你愿意,你可以将 C 的系列与 Pascal 的 Fortran (77, 66) 及其 WRITE 语句以及任何其他支持 I/O 的语言进行比较。intintsqrt(4.0)4.0printf()writeln
1赞 Steve Summit 1/24/2023
@jeaq 我认为这个问题现在已经得到了很好的回答,但不知道这是一个.编译器知道,但它无法将该信息传递给 。与任何函数调用一样,仅传递值。在几乎任何 ABI 中(今天比以前更是如此),如果一个函数试图使用与实际传递的类型不同的类型来访问传递的参数,你会得到毫无意义的结果。printfaintprintf
0赞 jeaq 1/25/2023
我明白了,谢谢大家。它基本上是这样的:我们在 printf 中指定我们想要打印的数据类型,一个整数,然后 printf 转到整数并找到名为 a 的变量并打印它。
0赞 jeaq 1/25/2023
我想困扰我的是,我在想,一旦我们使用变量(在本例中为 a),就是这样。不再是变量。但是没有考虑过我们可以在浮点数中拥有变量(a)的事实。
1赞 chux - Reinstate Monica 1/24/2023 #2

添加以指定它是一个整数有什么意义?%d

printf()是一个函数,它在格式参数之后接收各种类型的可变数量的参数。它不直接知道传递或接收的参数的数量或类型。

调用方知道它给出的参数计数和类型。printf()

为了传递参数计数和类型信息,调用方使用 format 参数对参数计数和类型进行编码。 使用该格式并对其进行解码,以了解参数计数和类型。传递的格式和后续参数保持一致非常重要printf()

3赞 Steve Summit 1/24/2023 #3

这个问题的答案只有在C的历史背景下才有意义。

到目前为止,C 语言是一种非常古老的语言。虽然它无疑是一种“高级语言”,但它是出了名的低级语言。而它最早的编译器是刻意的、自觉的、小而简单的。

在它的第一个化身中,C 在函数调用期间没有强制执行类型安全。例如,如果你调用了 ,你得到了错误的答案,因为需要一个 类型的参数,但是一个 .程序员有责任使用正确类型的参数调用函数:编译器不知道(甚至没有尝试跟踪)每个函数所需的参数,因此它没有也不能执行自动转换。(一个单独的程序 lint 可以检查函数是否使用正确的参数调用。sqrt(144)sqrtdouble144int

C++ 通过引入函数原型纠正了这一缺陷。这些在1989年的第一个ANSI C标准中被C继承。但是,函数原型仅适用于需要单个固定参数列表的函数,这意味着它无法帮助接受可变数量参数的函数,首要示例是: .printf

要记住的另一件事是,在 C 中,它或多或少是一个普通的函数。(“普通”,除了接受可变数量的参数。因此,编译器没有直接的机制来通知参数的类型,并使该类型列表可供 printf 使用。 在运行时无法知道在任何给定调用期间传递了哪些类型;它只能依赖(必须依赖)格式字符串中提供的线索。(这与许多语言形成鲜明对比,其中许多语言的语句是编译器解析的语言的显式部分,这意味着编译器可以执行任何需要做的事情,以便根据其已知类型正确处理每个参数。printfprintfprint

因此,根据语言的规则(受向后兼容性和语言历史的约束),编译器不能对调用中的参数执行任何特殊操作,只能执行所谓的默认参数提升。因此,如果您编写类似printf

int a = 7;
printf("%f", a);

诚然,这是一个令人不舒服的情况。如今,程序员已经习惯了函数原型提供的保护和隐式提升。如果,这些天,你可以打电话

int x = sqrt(144);

让正确的事情发生,你为什么不能同样打电话

printf("%f\n", 144);

好吧,你不能,尽管一个好的、现代的编译器无论如何都会试图帮助你。尽管编译器不必检查格式字符串(因为这是在运行时要完成的工作),并且不允许编译器插入任何隐式转换(默认提升除外,这在这里没有帮助),但编译器可以复制 的逻辑,检查格式字符串,并在程序员出错时发出强烈警告。例如,给定printfprintf

printf("%f\n", 144);

GCC 打印“警告:格式 '%F' 需要类型为'double'的参数,但参数 2 的类型为'int',而 clang 打印”警告:format 指定类型'double',但参数类型为'int'”。

在我看来,这是一个很好的折衷方案,平衡了 C 的传统行为和现代的期望。

评论

1赞 chux - Reinstate Monica 1/25/2023
“C++通过引入函数原型纠正了这一缺陷。这些是 C 在 1989 年的第一个 ANSI C 标准中继承的“——>小小的狡辩,在 C89 原型开始流行之前,但我不记得它是 C++ 继承而来的,因为它是一般的 C 改进。我使用的第一个C++“编译器”是将C++翻译成C,然后编译。当时的 C 编译器已经有原型了。