C 为什么这并没有给出编译错误 #包括 int main() { int i=10; int*常数p=&i; foo&p; printf(“%d\n”,*p); } 无效foo(整数**p) { int j=11; *p=&j; printf(“%d\n”,**p); }

C 为什么这并没有给出编译错误 #包括 int main() { int i=10; int*常数p=&i; foo&p; printf(“%d\n”,*p); } 无效foo(整数**p) { int j=11; *p=&j; printf(“%d\n”,**p); },c,C,p是指向变量x的常量指针,不能指向其他变量。但是为什么我们不在这里得到错误,输出是11 11?此代码没有违反约束,因此您可以从编译器得到的唯一结果是警告,例如gcc会给您: constptr.c:在函数“main”中: constptr.c:6:9:警告:函数“foo”的隐式声明[-Wimplicit函数声明] foo&p; ^ 施工图c:在顶层: constptr.c:9:10:警告:“foo”的类型冲突 无效foo(整数**p) ^ constptr.c:6:9:注意:前面的“foo”隐式声

p
是指向变量x的常量指针,不能指向其他变量。但是为什么我们不在这里得到错误,输出是
11 11

此代码没有违反约束,因此您可以从编译器得到的唯一结果是警告,例如
gcc
会给您:

constptr.c:在函数“main”中:
constptr.c:6:9:警告:函数“foo”的隐式声明[-Wimplicit函数声明]
foo&p;
^
施工图c:在顶层:
constptr.c:9:10:警告:“foo”的类型冲突
无效foo(整数**p)
^
constptr.c:6:9:注意:前面的“foo”隐式声明在这里
foo&p;
^
如果仔细查看这些警告,您会发现它是关于一个隐式声明的:
foo()
在使用之前没有原型,这是新的C标准所不允许的,但编译器仍然支持它的向后兼容性。在这种情况下,编译器假定原型是
intfoo(int)
。这就是下一个警告的原因(对于
foo
,类型冲突)

如果您正确地介绍了这样的原型:

#include <stdio.h>

int main()
{
    int i = 10;
    int *const p = &i;
    foo(&p);
    printf("%d\n", *p);
}

void foo(int **p)
{
    int j = 11;
    *p = &j;
    printf("%d\n", **p);
}
因此,现在编译器会警告您,转换会删除
const
。C不会强制您编写正确的代码,但无论如何您都应该这样做——警告告诉您您的代码不正确,可能会调用未定义的行为(这里就是这种情况)


尽管与您的问题无关,但您的代码包含了一个更糟糕的未定义行为:您在
main()
printf()
行)中的最后一行取消引用了一个指针,该指针现在指向自动存储持续时间的对象(aka:local variable
j
)这已经超出了范围,因此不再存在了!。编译器不太可能警告您这一点,但它仍然是desaster的配方。所以,在编写C代码时一定要非常小心



在此添加一条非常通用的建议:习惯于完全定义的“现代”编程语言(如Java、C#等)的人经常会问这样的问题:您的代码要么是正确的(并已定义),要么是错误的,如果是错误的,则会出现编译错误或运行时异常这不是C的工作方式在C中,任何遵循C语法且不违反语言约束的代码都可以编译,许多错误只会导致未定义的行为(这意味着在执行该代码时可能发生任何事情)。这意味着C“信任”程序员去做正确的事情——优点是可以从C源文件创建相当高效的本机语言代码,缺点是你要自己负责确保你的代码实际上是正确的。一个最好的做法是总是启用C编译器给你的任何警告(gcc的一个好设置是,例如,
-std=c11-Wall-Wextra-pedantic
),并且总是修复出现的任何警告。

这段代码没有违反约束,因此你唯一能从编译器得到的是警告,例如,
gcc
为您提供:

constptr.c:在函数“main”中:
constptr.c:6:9:警告:函数“foo”的隐式声明[-Wimplicit函数声明]
foo&p;
^
施工图c:在顶层:
constptr.c:9:10:警告:“foo”的类型冲突
无效foo(整数**p)
^
constptr.c:6:9:注意:前面的“foo”隐式声明在这里
foo&p;
^
如果仔细查看这些警告,您会发现它是关于一个隐式声明的:
foo()
在使用之前没有原型,这是新的C标准所不允许的,但编译器仍然支持它的向后兼容性。在这种情况下,编译器假定原型是
intfoo(int)
。这就是下一个警告的原因(对于
foo
,类型冲突)

如果您正确地介绍了这样的原型:

#include <stdio.h>

int main()
{
    int i = 10;
    int *const p = &i;
    foo(&p);
    printf("%d\n", *p);
}

void foo(int **p)
{
    int j = 11;
    *p = &j;
    printf("%d\n", **p);
}
因此,现在编译器会警告您,转换会删除
const
。C不会强制您编写正确的代码,但无论如何您都应该这样做——警告告诉您您的代码不正确,可能会调用未定义的行为(这里就是这种情况)


尽管与您的问题无关,但您的代码包含了一个更糟糕的未定义行为:您在
main()
printf()
行)中的最后一行取消引用了一个指针,该指针现在指向自动存储持续时间的对象(aka:local variable
j
)这已经超出了范围,因此不再存在了!。编译器不太可能警告您这一点,但它仍然是desaster的配方。所以,在编写C代码时一定要非常小心


在此添加一条非常通用的建议:习惯于完全定义的“现代”编程语言(如Java、C#等)的人经常会问这样的问题:您的代码要么是正确的(并已定义),要么是错误的,如果是错误的,则会出现编译错误或运行时异常这不是C的工作方式在C中,任何遵循C语法且不违反语言约束的代码都可以编译,许多错误只会导致未定义的行为(这意味着在执行该代码时可能发生任何事情)。这意味着C“信任”程序员去做正确的事情——优点是可以从C源文件创建相当高效的本机语言代码,缺点是你要自己负责确保你的代码实际上是正确的。
void foo3(int **p);


int main()
{
    int i = 10;
    int *const p = &i;
    foo3(&p);  // passing int * const * to parameter of type int ** discards const qualifiers
    printf("%d\n", *p);
}

void foo3(int **p)
{
    int j = 11;
    *p = &j;
    printf("%d\n", **p);
}