带有数组参数的const声明?

时间:2020-08-06 07:15:24

标签: arrays c pointers

因此,我正在阅读Kernighan Ritchie的语言C 一书,并在第39页第2章:类型,运算符和表达式 作者写道:

const声明也可以与数组参数一起使用,以指示该函数不会更改该数组:

int strlen(const char[]);

如果尝试更改const,则结果是实现定义的。

我不明白这是什么意思。如果有人可以简化他的意思,将不胜感激。

5 个答案:

答案 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[]的函数不能直接修改*ss[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语言中未定义的行为,这一事实使编译器可以进行此类优化(即,如果违反规则,则很难产生可预测的结果)。