为什么 scanf("%d") 可以处理转换为 int* 的 8 位数据类型,而 printf 不能?
Why does scanf("%d") work with an 8-bit datatype casted to an int*, but printf does not?
我试图通过将数据类型隐藏在 void*
后面并将其转换为%d
应该表示(即 int*
):
#include <stdio.h>
#include <stdint.h>
int main()
{
int8_t a, b;
void *v[2] = { &a, &b };
sscanf("-111,9\n", "%d,%d", (int*)v[0], (int*)v[1]);
printf("Works : %d, %d\n", a, b);
printf("Doesn't: %d, %d\n\n", *(int*)v[0], *(int*)v[1]);
return 0;
}
这是输出:
Works : -111, 9
Doesn't: 0, 9
问题:
- 为什么在转换为
int*
时可以 scanf()
读入 8 位类型,正如 a
和 [=21] 的直接 printf
验证的那样=]?不应该超限吗?
- 相反,当 scanf 可以读入它们时,为什么
printf()
无法打印被取消引用为 *(int*)v[0]
的 8 位类型?
- 由于格式说明符无疑是不够的,是否有一些编译器时间魔法可以告诉 scanf/printf 数据类型是什么?
我知道这段代码可能有误,但我仍然对示例背后的细节感到好奇。
感谢您的帮助!
上面的评论回答了问题,这里是摘要:
scanf
doesn't know what the types of its arguments are. - @Dia
- sscanf("-111,9\n", "%d,%d", &a, &b); does give a compiler warning if you enable compiler warnings. @RaymondChen
- 程序员有责任确保格式说明符与参数匹配。如果不是,则会发生未定义的行为。 - @GarrGodfrey
- “但是 scanf 如何在不破坏堆栈的情况下将 32 位 int ("%d") 写入 8 位类型?"要回答这个问题,您需要查看汇编代码。确定堆栈的布局方式,看看有什么被粉碎了。因为有东西被砸了,但一定不是什么重要的东西。使用 -S 编译以查看程序集,或使用调试器检查程序集。 – @user3386109
TL;DR:不保证执行 未定义行为 的操作会导致崩溃或诊断。它可能 看起来 有效。
Why is it possible scanf() read into an 8-bit type when casted to an int* as verified by the direct printf of a and b? Shouldn't it overrun?
它确实超限了——您正在将(可能)4 字节的值写入 1 字节的空间,因此您破坏了后面的 3 个字节。问题是,像这样的超出范围的写入是未定义的行为,它可能会崩溃,也可能不会但可能看起来有效。很可能他们只是在某处破坏了某些东西,这将导致一些后来的代码莫名其妙地崩溃,这很可能就是这里发生的事情(printf 调用崩溃或行为不当,因为 v
数组中的数据被 scanf 调用破坏了)。
如果我注释掉第二行 printf
并在 linux 上用 gcc 编译它,它会给我:
$ ./test
Works : -111, 9
*** stack smashing detected ***: ./test terminated
Aborted (core dumped)
这是完全一致的——带有不正确指针的 scanf 会导致未定义的行为,直到后面的代码尝试做某事(在这种情况下,当 main returns 并尝试清理其堆栈时才会出现)帧)。
Why is it possible for scanf()
read into an 8-bit type when casted to an int*
as verified by the direct printf of a
and b
? Shouldn't it overrun?
但它 确实 超限了,而且 a
和 b
最终包含了合理的值这一事实并不是任何形式的“验证”!
我这样修改了你的程序:
int8_t x1 = 11;
int8_t x2 = 22;
int8_t x3 = 33;
int8_t a;
int8_t x4 = 44;
int8_t x5 = 55;
int8_t x6 = 66;
int8_t b;
int8_t x7 = 77;
int8_t x8 = 88;
int8_t x9 = 99;
void *v[2] = { &a, &b };
printf("before: %d %d %d %d %d %d %d %d %d\n", x1, x2, x3, x4, x5, x6, x7, x8, x9);
sscanf("-111,9\n", "%d,%d", (int*)v[0], (int*)v[1]);
printf("Works : %d, %d\n", a, b);
printf(" after: %d %d %d %d %d %d %d %d %d\n", x1, x2, x3, x4, x5, x6, x7, x8, x9);
当我 运行 它时,我得到了这个输出:
before: 11 22 33 44 55 66 77 88 99
Works : -111, 9
after: -1 -1 -1 0 0 0 77 88 99
毫不奇怪,大多数 x
都被砸碎了。
(现在,没有 gua运行tee 编译器将一致地放置这些变量,所以这不是唯一可能的结果,但它非常清楚地表明,正如预期的那样,正在发生一些粉碎。)
I'm trying to figure out if scanf
can understand the datatype for %d
purely based on the format specifier by hiding the datatype behind a void*
and casting it to what %d
should mean (ie, int*
)
阅读规范如何?或者至少是手册页?尽管对于实验来说有一些话要说,但您所能期望的最好的结果就是了解您的特定实现如何做特定的事情。依靠良好的文档作为实验的基础可以使您的立足点更加稳固。
文档会支持这样的结论,即 scanf
完全依赖于出现在其格式字符串中的字段指令来判断第二个和后续参数的类型。如果您传递不正确类型的参数,则会导致未定义的行为。 scanf
期望对应于 %d
指令的参数是一个 int *
,就语言规范而言,void *
是不行的,更不用说指向的指针了int
.
以外的一些完整类型
现在,给定 C 实现提供的所有对象指针类型具有相同的大小和表示形式是相当普遍的,这样通过强制转换在它们之间进行转换不会影响值的表示形式。在这种情况下,您的转换本身虽然不正确,但可能不会对 scanf
造成实际问题。 (但它可以,原因可能是晦涩或神秘的。这就是 UB 的本质。)
然而,事实是指向的对象不是 int
,也不是与 int
兼容类型的对象,因此如果 scanf
尝试访问该对象由于这个原因,通过指针也会发生未定义的行为。这违反了“严格的别名规则”。与演员表不同,这个很可能在实践中引起明显的不当行为。
- Why is it possible
scanf()
read into an 8-bit type when casted to an int*
as verified by the direct printf
of a
and b
?
Shouldn't it overrun?
谁说不超限?您的程序有未定义的行为。这可以表现为做你期望的事情,或者看起来是这样做的。在这种特殊情况下,我倾向于猜测做正确事情的表现部分取决于您读取变量的顺序以及(我推断)您使用的是小端计算机这一事实,例如基于英特尔的。
- Conversely, why is
printf()
unable to print the 8-bit types that are dereferenced as *(int*)v[0]
's when scanf can read into
them?
您再次通过访问 a
和 b
来调用未定义的行为,就好像它们是 int
一样,而实际上它们不是。未定义的行为不必是一致的。或者它可能与在 C 语言级别不体现的结构和行为一致。这不是语言欠你解释的东西:这就是“未定义”的意思。
- Is there some compiler-time magic that tells scanf/printf what the datatype is since the format specifier is undoubtedly
insufficient?
格式字符串是语言规范所要求的全部,它绝对足以告诉 scanf
和 printf
期望的内容。使他们能够 验证 参数类型实际上是他们被告知期望的是不够的,但如果他们有这样的能力那么他们就不需要格式字符串来告诉他们首先了解类型。提供与格式字符串匹配的参数是程序员的责任,这并不繁重,因为程序员自己也提供了格式字符串。在 scanf
的情况下,程序员也有责任提供 scanf
可以在不违反严格的别名规则的情况下使用的有效指针值。该语言指定了当您正确执行此操作时会发生什么;当你做错时,它不会承诺任何特定的行为。
我试图通过将数据类型隐藏在 void*
后面并将其转换为%d
应该表示(即 int*
):
#include <stdio.h>
#include <stdint.h>
int main()
{
int8_t a, b;
void *v[2] = { &a, &b };
sscanf("-111,9\n", "%d,%d", (int*)v[0], (int*)v[1]);
printf("Works : %d, %d\n", a, b);
printf("Doesn't: %d, %d\n\n", *(int*)v[0], *(int*)v[1]);
return 0;
}
这是输出:
Works : -111, 9
Doesn't: 0, 9
问题:
- 为什么在转换为
int*
时可以scanf()
读入 8 位类型,正如a
和 [=21] 的直接printf
验证的那样=]?不应该超限吗? - 相反,当 scanf 可以读入它们时,为什么
printf()
无法打印被取消引用为*(int*)v[0]
的 8 位类型? - 由于格式说明符无疑是不够的,是否有一些编译器时间魔法可以告诉 scanf/printf 数据类型是什么?
我知道这段代码可能有误,但我仍然对示例背后的细节感到好奇。
感谢您的帮助!
上面的评论回答了问题,这里是摘要:
scanf
doesn't know what the types of its arguments are. - @Dia- sscanf("-111,9\n", "%d,%d", &a, &b); does give a compiler warning if you enable compiler warnings. @RaymondChen
- 程序员有责任确保格式说明符与参数匹配。如果不是,则会发生未定义的行为。 - @GarrGodfrey
- “但是 scanf 如何在不破坏堆栈的情况下将 32 位 int ("%d") 写入 8 位类型?"要回答这个问题,您需要查看汇编代码。确定堆栈的布局方式,看看有什么被粉碎了。因为有东西被砸了,但一定不是什么重要的东西。使用 -S 编译以查看程序集,或使用调试器检查程序集。 – @user3386109
TL;DR:不保证执行 未定义行为 的操作会导致崩溃或诊断。它可能 看起来 有效。
Why is it possible scanf() read into an 8-bit type when casted to an int* as verified by the direct printf of a and b? Shouldn't it overrun?
它确实超限了——您正在将(可能)4 字节的值写入 1 字节的空间,因此您破坏了后面的 3 个字节。问题是,像这样的超出范围的写入是未定义的行为,它可能会崩溃,也可能不会但可能看起来有效。很可能他们只是在某处破坏了某些东西,这将导致一些后来的代码莫名其妙地崩溃,这很可能就是这里发生的事情(printf 调用崩溃或行为不当,因为 v
数组中的数据被 scanf 调用破坏了)。
如果我注释掉第二行 printf
并在 linux 上用 gcc 编译它,它会给我:
$ ./test
Works : -111, 9
*** stack smashing detected ***: ./test terminated
Aborted (core dumped)
这是完全一致的——带有不正确指针的 scanf 会导致未定义的行为,直到后面的代码尝试做某事(在这种情况下,当 main returns 并尝试清理其堆栈时才会出现)帧)。
Why is it possible for
scanf()
read into an 8-bit type when casted to anint*
as verified by the direct printf ofa
andb
? Shouldn't it overrun?
但它 确实 超限了,而且 a
和 b
最终包含了合理的值这一事实并不是任何形式的“验证”!
我这样修改了你的程序:
int8_t x1 = 11;
int8_t x2 = 22;
int8_t x3 = 33;
int8_t a;
int8_t x4 = 44;
int8_t x5 = 55;
int8_t x6 = 66;
int8_t b;
int8_t x7 = 77;
int8_t x8 = 88;
int8_t x9 = 99;
void *v[2] = { &a, &b };
printf("before: %d %d %d %d %d %d %d %d %d\n", x1, x2, x3, x4, x5, x6, x7, x8, x9);
sscanf("-111,9\n", "%d,%d", (int*)v[0], (int*)v[1]);
printf("Works : %d, %d\n", a, b);
printf(" after: %d %d %d %d %d %d %d %d %d\n", x1, x2, x3, x4, x5, x6, x7, x8, x9);
当我 运行 它时,我得到了这个输出:
before: 11 22 33 44 55 66 77 88 99
Works : -111, 9
after: -1 -1 -1 0 0 0 77 88 99
毫不奇怪,大多数 x
都被砸碎了。
(现在,没有 gua运行tee 编译器将一致地放置这些变量,所以这不是唯一可能的结果,但它非常清楚地表明,正如预期的那样,正在发生一些粉碎。)
I'm trying to figure out if
scanf
can understand the datatype for%d
purely based on the format specifier by hiding the datatype behind avoid*
and casting it to what%d
should mean (ie,int*
)
阅读规范如何?或者至少是手册页?尽管对于实验来说有一些话要说,但您所能期望的最好的结果就是了解您的特定实现如何做特定的事情。依靠良好的文档作为实验的基础可以使您的立足点更加稳固。
文档会支持这样的结论,即 scanf
完全依赖于出现在其格式字符串中的字段指令来判断第二个和后续参数的类型。如果您传递不正确类型的参数,则会导致未定义的行为。 scanf
期望对应于 %d
指令的参数是一个 int *
,就语言规范而言,void *
是不行的,更不用说指向的指针了int
.
现在,给定 C 实现提供的所有对象指针类型具有相同的大小和表示形式是相当普遍的,这样通过强制转换在它们之间进行转换不会影响值的表示形式。在这种情况下,您的转换本身虽然不正确,但可能不会对 scanf
造成实际问题。 (但它可以,原因可能是晦涩或神秘的。这就是 UB 的本质。)
然而,事实是指向的对象不是 int
,也不是与 int
兼容类型的对象,因此如果 scanf
尝试访问该对象由于这个原因,通过指针也会发生未定义的行为。这违反了“严格的别名规则”。与演员表不同,这个很可能在实践中引起明显的不当行为。
- Why is it possible
scanf()
read into an 8-bit type when casted to anint*
as verified by the directprintf
ofa
andb
? Shouldn't it overrun?
谁说不超限?您的程序有未定义的行为。这可以表现为做你期望的事情,或者看起来是这样做的。在这种特殊情况下,我倾向于猜测做正确事情的表现部分取决于您读取变量的顺序以及(我推断)您使用的是小端计算机这一事实,例如基于英特尔的。
- Conversely, why is
printf()
unable to print the 8-bit types that are dereferenced as*(int*)v[0]
's when scanf can read into them?
您再次通过访问 a
和 b
来调用未定义的行为,就好像它们是 int
一样,而实际上它们不是。未定义的行为不必是一致的。或者它可能与在 C 语言级别不体现的结构和行为一致。这不是语言欠你解释的东西:这就是“未定义”的意思。
- Is there some compiler-time magic that tells scanf/printf what the datatype is since the format specifier is undoubtedly insufficient?
格式字符串是语言规范所要求的全部,它绝对足以告诉 scanf
和 printf
期望的内容。使他们能够 验证 参数类型实际上是他们被告知期望的是不够的,但如果他们有这样的能力那么他们就不需要格式字符串来告诉他们首先了解类型。提供与格式字符串匹配的参数是程序员的责任,这并不繁重,因为程序员自己也提供了格式字符串。在 scanf
的情况下,程序员也有责任提供 scanf
可以在不违反严格的别名规则的情况下使用的有效指针值。该语言指定了当您正确执行此操作时会发生什么;当你做错时,它不会承诺任何特定的行为。