【问题标题】:Why i should incorporate a length modifier as argument in scanf()-family members? What is the benefit of? What does scanf with the length modifier do?为什么我应该在 scanf()-family 成员中加入长度修饰符作为参数?有什么好处?带有长度修饰符的 scanf 有什么作用?
【发布时间】:2020-02-22 16:29:48
【问题描述】:

可以选择将长度修饰符作为参数合并到scanf() 或其家族成员之一的语句中。

为什么我应该在语句中准确地实现一个长度修饰符,无论我是否已经将分配变量声明为,例如 long int,正确与否?

我完全不明白为什么我应该这样做,并且在包含它和不使用长度修饰符的情况下没有看到输出有任何区别。

例如%2ld里面的l(ell):

long int i;
fscanf(stdin,"%2ld",&i);

我了解长度修饰符是什么,但不了解实现它的原因。

我对它进行了一些研究,即使是在 C99 中,但找不到任何关于它真正详细功能的详细描述。

在此site 上声明:

长度(-修饰符)

hh、h、l、ll、j、z、t、L 之一(可选)。 这会改变相应参数所指向的预期存储类型(见下文)。

这是否意味着长度修饰符改变了分配变量的类型,如果它实际上与转换说明符定义的格式值不匹配?

带有长度修饰符的scanf()到底是做什么的?

有什么好处?

非常感谢您收听。

【问题讨论】:

  • 如果预先知道长度,则无需扫描终止空字节。
  • 它可以防止类型不匹配,它试图将 32 位填充到一个短整数中。
  • %d 是 UB,它可能对您有用,因为 sizeof(long) == sizeof(int) 在 32 位代码中。在 64 位代码中不一定如此。不要被类型提升弄糊涂了,它让你对 printf() 很草率,指针不会被提升。

标签: c++ c scanf modifier


【解决方案1】:

编辑:

我很抱歉假设这很明显,但“不要忘记忽略警告”并没有被认真对待。事实上,我认为这只是重新编写程序的一个原因。


编译以下程序(不要忘记忽略警告!)

#include <stdio.h>

int main ()
{
    long int i;
    long int j;
    scanf("%d", &i);
    scanf("%ld", &j);
    printf("i == %ld\n", i); 
    printf("j == %ld\n", j);
    return 0;
}

运行它并提供一个大于 2147483647 的整数两次,你就会得到答案。


剧透:

我使用 4294967297 执行此操作,然后打印出以下内容:

i == 1
j == 4294967297

当然,答案是特定于体系结构的,我假设您的机器以 32 位存储 int。但是如果有理由使用long int 来存储数字,最好使用长度修饰符(如果没有区别,则不需要使用long int)。

但要更明确地回答您的问题:

这是否意味着长度修饰符改变了分配变量的类型,如果它实际上与转换说明符定义的格式值不匹配?

没有。正如您在尝试存储应该适合 long int 的值而不使用长度修饰符时所看到的那样,就像您正在存储到 int 一样(甚至表现得好像发生了溢出一样)。如果您使用%ldscanf 尝试将值存储到int 变量中,则不会引发段错误,这可能会导致坏事。编译以下内容(并且为了实验而不得不忽略警告):

#include <stdio.h>

int main ()
{
    int k[2] = {0,0};
    printf("k[0] == %d\n", k[0]);
    printf("k[1] == %d\n", k[1]);
    scanf("%ld", &k);
    printf("k[0] == %d\n", k[0]);
    printf("k[1] == %d\n", k[1]);
    return 0;
} 

并通过输入 4294968300 打印出来运行它:

k[0] == 0
k[1] == 0
4294968300 
k[0] == 1004
k[1] == 1

意思是臭名昭著的scanf 已经将k[0] 写入k[1] 完全没有引起注意!

为什么我应该 [sic] 在语句中准确地实现一个长度修饰符,无论我 [sic] 是否已经将分配变量声明为,例如 long int,正确与否?

我完全不明白为什么我 [原文如此] 应该这样做,并且在包含它和不使用长度修饰符的情况下没有看到输出有任何区别。

我认为上面的例子应该给你一个强烈的动力,让你在尝试在scanf 调用中使用不匹配的长度修饰符进行编译时不要忽略警告。否则,您甚至可能面临安全风险。

【讨论】:

  • “不要忘记忽略警告” - 这是可怕的建议,总是
  • @JesperJuhl 我是在讽刺
  • @JesperJuhl 这绝对正确。但是这个陈述只是对我的问题的答案的正确解释,在一个“虚拟”程序上练习。该程序无意超出此目的使用它,因此我认为带有忽略警告的声明在这里绝对合适,因为这是对我的问题的一个很好的答案。
【解决方案2】:

scanf 需要知道它正在写入什么类型的对象。假设在这个 C 实现中,char 是一个字节,short 是两个字节,int 是四个字节,long int 是八个字节。 scanf 接收的参数是一个指针,但 scanf 知道指针指向的唯一方法是通过格式字符串。

如果参数指向long int,则scanf 需要向该位置写入八个字节。如果它指向short,那么scanf 需要向该位置写入两个字节而不是八个字节。

长度修饰符只是告诉scanf 类型的代码的一部分。我们可以编写一个代码,其中d 代表intD 代表long intS 代表short int,等等。或者我们可以制作其他代码。只是我们选择的代码使用d代表inthd代表short intld代表long int,等等。

另一种选择可能是将有关目标类型的信息与有关要解析的内容的信息分开。 d 表示int 和“默认读取十进制”。更简洁的设计可能有一个类型代码(shortint 等)和另一个输入格式代码(十进制、八进制、十六进制等)。

指针本身也有可能具有不同的表示形式,具体取决于它所指向的事物的类型。这在现代 C 实现中是不寻常的,但这是可能的。所以scanf 使用长度修饰符来知道它正在写入什么类型的对象以及传递给它的指针类型。

【讨论】:

  • 但是如果类型显着不同会是什么结果?假设指向的对象的类型是int 并且赋值是long 类型,编译器会让它通过吗?程序是否会写入超出变量在内存中分配空间边界的数据溢出,这被官方归类为未定义行为?如果指向的对象和分配的值在类型上没有区别,我是否也应该合并长度修饰符?为什么我要始终实现长度修饰符?
  • @RobertS:当程序在参数类型和格式说明符之间存在不匹配时,高质量的编译器会警告您(除非格式字符串不是常量并且编译器无法确定格式说明符)。如果您不顾警告继续编译和执行这样的程序,它可能会出现异常,因为scanf 通常会在给定对象之外写入数据。您需要对长度修饰符做的所有事情就是将它与您传递的参数的类型相匹配。
  • 一句话:每个人都应该提供长度修饰符,如果他/她正在实施 scanf() 或其家庭成员之一以保持程序安全。
  • @RobertS:如果参数类型不是格式说明符的默认值,您需要一个长度修饰符。如果参数类型是格式说明符的默认值(例如 int 代表 %d),则不需要长度修饰符。
  • @RobertS 看到我的回答,我昨天在那里确认了埃里克警告你的确切内容
猜你喜欢
  • 2021-09-13
  • 2010-09-13
  • 1970-01-01
  • 2011-02-15
  • 1970-01-01
  • 2019-10-23
  • 2012-11-24
  • 2011-08-27
  • 1970-01-01
相关资源
最近更新 更多