使用const int* const 的scanf可以工作,但不应该这样做

15

我有类似以下代码的等价物:

const int* const n = new int;
printf("input: ");
scanf("%d", n);
delete n;

现在,由于n是指向一个常量整数的指针,所以这应该是不起作用的(我期望会有编译器错误)。然而,这似乎可以正常工作,甚至将输入的值存储到*n中。

我想知道,为什么这不会给我一个错误;为什么它会工作?难道scanf不应该无法更改*n的值吗?

5个回答

23

scanf的原型是:

int scanf ( const char * format, ... );

省略号表示它可以接受可变数量的参数。C(和C ++)没有办法检查这些参数的有效性。这就是为什么如果您在这里传递double的地址甚至是double变量本身,您不会收到错误。程序员需要验证传递了正确的参数。


“C(和C++)没有办法检查这些参数的有效性”这不是完全正确的,上次我检查时,Clang用警告报告了错误的scanf使用情况。但事实上,没有通过类型系统来检查其有效性的方法。 - cubuspl42
据我所知,大多数编译器即使是可变参数函数,也会警告const,因为没有一个参数可以是const,而且对于格式字符串还有特殊的检查。我认为很有可能也有这种检查。但我不太确定,因为我几乎从不使用scanf() - Iharob Al Asimi
1
例如尝试将unsigned int传递给printf(),其中使用了"%f"格式说明符。 - Iharob Al Asimi
10
这归功于编译器(以及库头文件中的函数原型被标注出来),但不是语言标准的一部分。 - too honest for this site

5

scanf 几乎没有类型安全性,它会快乐地执行你告诉它的操作。这是因为在 C 语言中实现可变参数列表的方式。它们期望类型与您告诉它的类型相同。

因此,如果您给 scanf 提供不匹配的转换说明符,就会引发未定义的行为,这将在运行时发生。同样,编译器可能无法确定传递的指针是否为 const type* const 类型。一个好的编译器可能会在发现一些奇怪的东西时发出诊断,但并不一定要这样做。

由于大多数未定义行为都发生在运行时,因此通常由程序员负责了解各种形式的未定义行为并避免它们。


最好完全避免使用scanf和可变参数列表,因为它们缺乏类型安全等问题。 - Lundin
然而,该对象并不是“const”:它是使用“new int”创建的。 - user1084944
@Hurkyl 在 C 和 C++ 中,您始终可以应用更严格的 const。拥有一个指向非常量对象的 const 指针是完全可以的。 - Lundin
是的,但重点在于被修改的对象不是一个const对象,尽管地址是通过指向“const int”的指针获得的。 - user1084944
缺少 &scanf 试图修改非常量 int 对象而不是常量(且非 int)指针对象的原因。 - user1084944
@Hurkyl 啊,好的,是我看错了问题。等一下。 - Lundin

3

由于scanf是可变参数的,因此几乎可以传入任何内容,因此它应该也可以编译通过。

虽然n是指向const int的指针,但它指向的对象实际上并不是一个const对象。因此,修改该int对象(例如使用const_cast将指针转换为int*)是良好定义的行为。

最后,fscanf的标准文档如下:

将首个跟在格式参数后且还没有接收到转换结果的参数所指向的对象中放置转换结果。如果该对象没有相应的类型,或者无法将转换结果表示为对象,则其行为是未定义的。

指针实际上是指向适当类型的对象;总之,我认为这是良好定义的(但令人困惑)行为。


2

编译器可以发出警告,但不会阻止您犯这样的错误,这是未定义行为,const只是用作指示符,它不能防止编译。而且它能正常工作是因为指针并不真正是const,尽管传递const指针是未定义行为,但这并不是真正的情况。


2
你错过了问题的核心 - scanf 的可变参数本质。@pcarter 没有。 - cubuspl42
1
可能只是想澄清一下,通常情况下这是未定义的行为,但在他的特定情况下,这是已定义的行为,因为 int 确实是非 const 的。 - Johannes Schaub - litb
@ᐅJohannesSchaub-litbᐊ 我想我已经这样做了,这就是为什么我说“严格”。 - Iharob Al Asimi
这正好相反。即使出现警告,他的代码在严格上也是有定义行为的。 - Johannes Schaub - litb
其实,我对定义行为并不是非常确定,也许有人可以澄清这个细微之处。但并不是因为写入,而是因为int *const int *是不同的类型。据我所知,它们具有相同的表示,并且允许重新解释彼此的字节(从C++11开始,严格来说,它们添加了别名例外)。但它取决于详细描述中的printf函数。如果它们需要精确的类型匹配,并且没有余地,那么就是未定义的行为(虽然如果失败了,我会非常惊讶)。 - Johannes Schaub - litb
显示剩余3条评论

1

好的,scanf 不会区分传递的参数,但如果启用警告编译器将会提示 -

 warning: writing into constant object (argument 2) [-Wformat]

这里没有修改任何const对象。我们有一个(常量)指向const int的指针,但它指向一个非const对象,因此修改引用是可以的。 - user1084944
@Hurkyl 但是它不会写入指针本身吗?那之前是我的误解,因此撤回。 - ameyCU
它通过指针进行写入,但不修改指针本身。 - user1084944
@Hurkyl 哎呀!可能有问题,但在这种情况下不会有未定义行为(虽然不确定)。 - ameyCU
这里的警告是编译器的错误吗? - paulm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接