因此,我正在阅读Kernighan Ritchie的语言C 一书,并在第39页第2章:类型,运算符和表达式 作者写道:
const声明也可以与数组参数一起使用,以指示该函数不会更改该数组:
int strlen(const char[]);
如果尝试更改const,则结果是实现定义的。
我不明白这是什么意思。如果有人可以简化他的意思,将不胜感激。
答案 0 :(得分:1)
“定义的实现”仅表示应该由实现决定。与“未定义的行为”的区别在于,当它是“实现定义的”时,该行为需要记录在案。在此处详细了解:Undefined, unspecified and implementation-defined behavior
但是,如果将其强制转换为非const,则可以通过const指针进行更改。这将打印42;
void foo(const int *x)
{
*(int *)x = 42;
}
int main(void)
{
int n = 69;
foo(&n);
printf("%d\n", &n);
}
我写了一个有关const的相关答案,您可以在这里阅读:https://stackoverflow.com/a/62563330/6699433
答案 1 :(得分:0)
将函数参数声明为const
表示函数不应更改这些参数的值。
即使C通过值传递参数,指向的值也易于更改。通过将函数参数声明为const
,如果函数尝试修改指向的值,则编译器将生成错误。
以下函数将更改x指向的值:
void foo(int *x)
{
*x = 100;
}
在以下函数中,通过将参数标记为const
,该函数将无法更改x指向的值。
void foo(const int *x)
{
*x = 100; // Compiler generates an error
}
在C语言中,即使看起来在使用方括号[]
时正在传递数组,您实际上仍在传递指针。因此void foo(const int *x)
与void foo(const int x[])
答案 2 :(得分:0)
Kernighan和Ritchie是错误的;尝试修改const
对象是未定义的,不是实现定义的。
此规则仅适用于最初用const
定义的对象。
const
是建议性的,不是强制性的。如果未使用const
定义对象,则可以用C标准定义一个函数来修改用const
参数指向的对象。
引用的段落是错误的。尝试修改用const
定义的对象具有未定义的行为,而不是实现定义的行为。并且这仅适用于用const
定义的对象,不适用于通过const
限定的指针传递的对象,如果这些对象最初不是用const
定义的。
C 2018 6.7.3 7说:
如果试图通过使用具有非const限定类型的左值来修改以const限定类型定义的对象,则该行为是不确定的。
在C 1990 6.5.3中出现相同的措辞。
“未定义”表示C标准对行为没有任何要求(C 2018 3.4.3)。这与“实现定义”不同,后者意味着C实现必须记录如何在各种可能性之间做出选择(C 2018 3.4.1)。
请注意,该规则仅适用于使用const
定义的对象。 6.7 5告诉我们,对于对象标识符,定义是一个声明,该声明导致为该对象保留存储空间。如果我们在函数内声明int x;
,将导致为x
保留存储空间,因此它是一个定义。但是,语句int strlen(const char[]);
仅声明一个函数及其参数类型。未声明实际参数,因为没有名称。如果我们考虑实际的函数定义,例如:
int strlen(const char s[])
{
…
}
然后,此函数定义包含参数s
的声明。并且确实定义了s
;函数执行时将保留参数本身的存储。但是,此s
只是指向调用者传递其地址的某个对象的指针。因此,这不是该对象的定义。
到目前为止,我们知道6.7.3 7中的规则告诉我们,修改用const
定义的对象具有未定义的行为。关于函数修改通过指针const
接收到的对象的功能,还有其他规则吗?有。赋值运算符的左操作数必须是可修改的。 C 2018 6.5.16 2说:
赋值运算符的左值应为可修改的左值。
按照C 2018 6.3.2.1的规定,用const
限定的左值是不可修改的。此段是C标准的约束,这意味着需要C实现来诊断违规。 (因此,这又不是实现定义的行为。C实现必须生成一条消息。)++
和--
运算符(前后)都具有相似的约束。
因此,带有参数const char s[]
的函数不能直接修改*s
或s[i]
,至少在没有得到诊断消息的情况下不能。但是,如果程序最初不存在,则可以在转换运算符中删除const
。 C 2018 6.3.2.3 2说我们可以添加const
:
对于任何限定词 q ,指向非 q 限定类型的指针都可以转换为指向 q 限定类型的指针类型的版本;存储在原始指针和转换后的指针中的值应比较相等。
然后C 2018 6.3.2.3 7表示,完成此操作后,我们可以将const
版本转换回原始类型:
指向对象类型的指针可能会转换为指向不同对象类型的指针……再次转换时,结果应等于原始指针。
这意味着如果调用例程具有:
int x = 3;
foo(&x);
printf("%d\n", x);
和foo
是:
void foo(const int *p)
{
* (int *) p = 4;
}
然后这是C标准允许和定义的。函数foo
删除const
并修改其指向的对象,然后将打印“ 4”。
这里的一个教训是函数参数中的const
是建议性的,不是C强制执行的。它有两个目的:
const
通常是对人类的一种指示,表明该函数不会通过该参数修改指向的对象。 (但是,在某些情况下(此处未讨论)该指示不成立。)const
类型进行修改。这可以防止由于印刷错误而可能导致不必要地分配给const
对象的疏忽错误。但是,允许某个函数显式删除const
,然后尝试修改该对象。答案 3 :(得分:-1)
const char[]
作为函数的参数类型实际上等于指向const char
(类型const char *
)的指针。数组符号是为了将指针传递给数组时的方便而发明的。
相关帖子:
通过声明const char p_a[]
,您将p_a
声明为指向const char
的指针。
与const
关联的char
限定词告诉编译器,不应修改调用方中指向的char
对象/数组。
任何尝试使用非const
指针/左值来修改此对象/数组的行为都会导致未定义的行为:
“如果试图通过使用具有非const限定类型的左值来修改以const限定类型定义的对象,则该行为是不确定的。”
来源:C18,6.7.3 / 7
,编译器通常会在这样做时警告您。
strlen
不需要,也不应修改将指针作为参数传递给的字符串。为了不给调用者意外修改字符串的机会,该指针被归类为指向const char
的指针。
const
限定符增加了一层额外的安全保护。
答案 4 :(得分:-1)
该材料似乎已过时。
strlen
标准库函数现在返回size_t
,但无论如何:
int strlen(const char[]);
,与int strlen(const char*);
相同
strlen可以接受char*
或const char*
,而无需强制转换。
如果将指针传递给非常量变量,并且该函数尝试对其进行修改(通过像const
中抛弃void modify(char const *X){ *(char*)X='x'; }
),则行为是̶i̶m̶p̶l̶e̶m̶e̶n̶t̶a̶t̶i̶o̶n̶-̶d̶e̶f̶i̶n̶e̶d̶未定义(它是< strong> undefined ,而不是较新的C版本中定义的实现。
未定义的行为意味着您将失去对该程序的所有保证。
定义的实现意味着代码将以一致,可预测的方式(给定平台)执行特定的操作(例如,中止,段错误,不执行任何操作),并且该操作不会影响程序其余部分的完整性。
在较老的头脑简单的编译器中,如果平台具有只读内存,则将静态生存期const
变量放置在只读内存中,否则将其放置在可写内存中。然后,您将遇到尝试修改它的段错误,或者不会。那将是实现定义的行为。
较新的编译器可能还会尝试基于const
注释进行深远的优化,因此很难说出这种修改尝试可能带来的影响。这种修改尝试是现代C语言中未定义的行为,这一事实使编译器可以进行此类优化(即,如果违反规则,则很难产生可预测的结果)。