为清晰起见,是否应使用返回类型的无用类型限定符?

时间:2009-10-16 17:47:37

标签: c coding-style const static-analysis

当我们在头文件中有原型时,我们的静态分析工具会抱怨“返回类型上的无用类型限定符”,例如:

const int foo();

我们以这种方式定义它,因为函数返回一个永远不会改变的常量,认为API似乎更清晰const

我觉得这类似于为了清晰明确地将全局变量初始化为零,即使C标准已经声明如果没有显式初始化所有全局变量将被初始化为零。在一天结束时,它真的没关系。 (但静态分析工具并没有抱怨这一点。)

我的问题是,有什么理由可以导致问题吗?我们是否应该忽略该工具产生的错误,或者我们是否应该以不太清晰和一致的API的可能成本来安抚该工具? (它返回工具没有问题的其他const char*常量。)

6 个答案:

答案 0 :(得分:25)

通常情况下,您的代码可以更准确地描述正在发生的事情。您收到此警告是因为const中的const int foo();基本上没有意义。如果您不知道const关键字的含义,那么API似乎更清晰。不要超载这样的含义; static已经足够糟糕,而且没有理由增加混淆的可能性。

const char *表示与const int不同的内容,这就是为什么您的工具不会抱怨它。前者是指向常量字符串的指针,这意味着调用返回该类型的函数的任何代码都不应该尝试修改字符串的内容(例如,它可能在ROM中)。在后一种情况下,系统无法强制您不对更改的int进行更改,因此限定符无意义。与返回类型更接近的是:

const int foo();
char * const foo2();

这将导致静态分析发出警告 - 向返回值添加const限定符是无意义的操作。只有具有引用参数(或返回类型)时才有意义,例如const char *示例。

事实上,我刚做了一个小测试程序,GCC甚至明确警告过这个问题:

test.c:6: warning: type qualifiers ignored on function return type

所以不只是你的静态分析程序在抱怨。

答案 1 :(得分:4)

您可以使用其他技术来说明您的意图,而不会让工具不快。

#define CONST_RETURN

CONST_RETURN int foo();

const char *没有问题,因为它声明了一个指向常量字符的指针,而不是一个常量指针。

答案 2 :(得分:4)

暂时忽略constfoo()会返回一个值。你可以做到

int x = foo();

并将foo()返回的值分配给变量x,其方式与您可以做的非常相似

int x = 42;

将值42赋给变量x。
但您无法更改42 ...或foo()返回的值。说foo()返回的值无法更改,将const关键字应用于foo()类型无法实现。

不能是constrestrictvolatile )。只有对象可以有类型限定符。


对比
const char *foo();

在这种情况下,foo()返回指向对象的指针。返回值指向的对象可以是const

答案 3 :(得分:3)

const int foo()const char* foo()非常不同。 const char* foo()返回一个数组(通常是一个字符串),其内容不允许更改。想想之间的区别:

 const char* a = "Hello World";

const int b = 1;

a仍然是一个变量,可以分配给其他无法更改的字符串,而b不是变量。所以

const char* foo();
const char* a = "Hello World\n";
a = foo();

是允许的,但

const int bar();
const int b = 0;
b = bar();
即使const声明为bar(),也不允许

答案 4 :(得分:3)

copy 返回int。它可能是const的副本,但是当它被分配给其他东西时,由于它是可分配的,这个东西不能定义为const。

关键字const在语言中具有特定的语义,而在这里你滥用它本质上是一个注释。而不是增加清晰度,而是暗示对语言语义的误解。

答案 5 :(得分:2)

是。我建议“明确地”编写代码,因为在阅读代码时,任何人(包括你自己)都会清楚地表达你的意思。您正在编写其他程序员的代码来阅读,而不是为了取悦编译器和静态分析工具的想法!

(但是,您必须要小心,任何此类“不必要的代码”都不会导致生成不同的代码!)

显式编码提高可读性/可维护性的一些示例:

  • 我在算术表达式的部分周围放置括号,以明确指定我想要发生的事情。这使得任何读者都能清楚地了解我的意思,并使我不必担心优先规则(或犯错误):

    int a = b + c * d / e + f;      // Hard to read- need to know precedence
    int a = b + ((c * d) / e) + f;  // Easy to read- clear explicit calculations
    

  • 在C ++中,如果覆盖虚函数,那么在派生类中,您可以声明它而不提任何“虚拟”。任何阅读代码的人都不能说它是一个虚函数,这可能是灾难性的误导!但是,您可以安全地使用虚拟关键字:

    virtual int MyFunc()
    ,这使得阅读您的类标题的人明白此方法是虚拟的。 (这个“C ++语法错误”在C#中通过要求在这种情况下使用“override”关键字得到修复 - 如果有人需要它更多证明错过了“不必要的虚拟”是一个非常糟糕的主意)

这些都是明显的例子,其中添加“不必要的”代码将使代码更具可读性并且不易出错。