【问题标题】:In C, is it legal to add `const` only in function definitions, not declarations?在 C 语言中,仅在函数定义中而不是声明中添加 `const` 是否合法?
【发布时间】:2017-09-22 21:11:18
【问题描述】:

是否向标准允许的函数参数添加额外的 const 说明符,如下所示?

foo.h:

int foo(int x, char * data);

foo.c:

// does this match the prototype?
int foo(const int x, char * const data) {
    // this implementation promises not to change x or move data inside the function
}

GCC 通过-std=c99 -Wpedantic -Wall -Werror 接受它,但这不一定与标准兼容。

This answer 表明 C++ 标准允许这样做 - C (99) 标准是否也允许这样做?


还有另一个问题here 和一个很好的答案here for C++

【问题讨论】:

  • 这是一个有趣的问题,但我认为允许它不是一个好主意,如果是,那就不好了。为什么您希望您的定义与声明不匹配?它只会让代码更加混乱。
  • 它是允许的(参见“§compatible types”,但非常糟糕的做法。这样的代码可能会被专业人士拒绝,因为它可能存在其他缺陷(永远不会只有一只蚂蚁)。它也可能会混淆像 Doxygen 这样的常用工具。那么,支持@savram 到底为什么要这样做?
  • 因为函数原型不应该包含实现细节,参数是否为const是一个实现细节。根据this answer,Herb Sutter 的 Exceptional C++ 实际上建议您这样做。
  • 可以说真正的问题不是为什么语言允许你在声明中省略const,而是为什么它允许你把它放在那里。对于声明的消费者来说,这充其量只是毫无意义的噪音。在最坏的情况下,这是在声明外部函数时出错的一种方式。

标签: c constants c99


【解决方案1】:

函数参数列表规则中的特殊情况明确允许这样做。 N1570 §6.7.6.3p131 说:

在确定类型兼容性和复合类型时,每个声明为函数或数组类型的参数都被视为具有调整后的类型,每个声明为合格类型的参数都被视为具有其声明类型的非限定版本。

但你也必须明白,像const char * 这样的类型的“非限定版本”仍然是const char *,因为类型const char * 派生自类型const char,并且§6.2.5p26

派生类型不受其派生类型的限定符(如果有)限定。

这意味着声明

void foo (const int x);

符合定义

void foo (int x) { ... }

但声明

void bar (const char *x)

符合定义

void foo (char *x) { ... }

您可能想知道为什么这些规则是这样的。简短的版本是,在 C 中,所有参数总是通过复制值来传递(但不是值所指向的任何数据,如果涉及指针的话),所以无论是否实际参数是const T;无论如何,被调用者将其作为普通的旧T 接收。但是如果你复制一个指向常量数据的指针,副本仍然指向常量数据,所以它确实很重要并且应该保留那个限定符。


1 文档 N1570 是最接近 2011 年 ISO C 标准的近似值,可免费公开获得。

据我所知,这些规则自 1989 年的原始标准以来并未发生重大变化。 C89 之前的“K&R”C 没有原型,也没有const,所以整个问题都没有实际意义。

【讨论】:

  • 我已经用一些指针更新了这个问题,以表明我不是试图从指针中删除/添加限定符
  • 当我专门询问 C99 时,我会接受这个,因为它是一个更普遍有用的答案 - 感谢您的全面解释。
  • @Eric 是的,我不认为你是,但我认为让我的回答尽可能清楚很重要 which consts 可以在声明和定义。
  • @Eric 据我所知,这些规则自 1989 年最初的 C 标准以来没有改变。
  • 如果您认为忽略的 cv 限定符仅影响函数定义中的 局部变量const 还是 volatile,这一切就更有意义了。它们与函数的接口无关。
【解决方案2】:

来自C99 spec,6.7.5.3.15 函数声明符,这是合法的:

如果一种类型具有参数类型列表,而另一种类型由包含(可能为空)标识符列表的函数定义指定,则两者在参数数量上应一致,并且每个原型参数的类型应兼容具有将默认参数提升应用到相应标识符的类型而产生的类型。 (在确定类型兼容性和复合类型时,每个声明为函数或数组类型的参数都被视为具有调整后的类型,而每个声明为限定类型的参数都被视为具有其声明类型的非限定版本。 )

【讨论】:

    【解决方案3】:
    参数上的

    cv-qualifiers(而不是参数的类型)不影响参数的类型,所以不影响原型。像这样不影响原型的事情可以在声明和定义之间有所不同,没有问题。同理,参数名称不影响原型,所以声明和定义也可以不同。

    现在令人困惑的是,这里出现的const 可能在参数上,也可能在参数类型上,具体取决于它的确切位置。如果它是参数的type的一部分,那么它确实会影响原型,所以声明和定义之间必须保持一致:

    int foo(const int x);   // const on the parameter
    int foo(int * const x);  // also const on the parater
    int foo(const int *x);  // const in the type, not on the parameter
    int foo(int const *x);  // also const in the type
    

    【讨论】:

    • 你是对的,但你的解释很糟糕,因为它做出了标准中不存在的区别。 const 是一个类型限定符,在您的所有四个示例声明中,它的存在会更改参数 x 的类型。但是参数列表有一个特殊情况:“使用限定类型声明的每个参数都被视为具有其声明类型的非限定版本”。加上char *const is char * 的非限定版本但const char * 的非限定版本仍然是const char * 的一般规则,这会产生您描述的行为。
    猜你喜欢
    • 2019-06-14
    • 1970-01-01
    • 2010-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-14
    • 1970-01-01
    • 2021-04-24
    相关资源
    最近更新 更多