12

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

const int foo();

我们这样定义它是因为函数返回一个永远不会改变的常量,认为 API 看起来更清晰const

为了清楚起见,我觉得这类似于将全局变量显式初始化为零,即使 C 标准已经声明如果未显式初始化,所有全局变量都将初始化为零。归根结底,这真的无关紧要。(但静态分析工具并没有抱怨这一点。)

我的问题是,这有什么原因会导致问题吗?我们应该忽略工具产生的错误,还是应该以不太清晰和一致的 API 为代价来安抚工具?(它返回const char*工具没有问题的其他常量。)

4

6 回答 6

26

通常最好让您的代码尽可能准确地描述正在发生的事情。您收到此警告是因为constinconst 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

因此,抱怨的不仅仅是您的静态分析程序。

于 2009-10-16T17:52:40.217 回答
5

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

#define CONST_RETURN

CONST_RETURN int foo();

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

于 2009-10-16T17:51:09.003 回答
4

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

int x = foo();

并将返回的值分配给foo()变量x,方法与您可以做的大致相同

int x = 42;

将值42赋给变量 x。
但是您不能更改42... 或由 . 返回的值foo()。说返回的值foo()不能改变,通过将const关键字应用于类型foo()什么都没有。

不能是const(restrict, 或volatile)。只有对象可以有类型限定符。


对比

const char *foo();

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

于 2009-10-16T18:14:24.510 回答
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().

于 2009-10-16T18:04:07.107 回答
3

int 由copy返回。它可能是一个 const 的副本,但是当它被分配给其他东西时,由于它是可分配的事实,某物不能根据定义成为 const。

关键字 const 在语言中具有特定的语义,而在这里您将其误用为本质上的注释。它没有增加清晰度,而是暗示了对语言语义的误解。

于 2009-10-16T18:48:28.917 回答
2

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

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

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

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

    int a = b + c * d / e + f; // 难以阅读 - 需要知道优先级
    int a = b + ((c * d) / e) + f; // 易于阅读 - 清晰的显式计算
    

  • 在 C++ 中,如果你重写了一个虚函数,那么在派生类中你可以声明它而根本不提及“虚”。任何阅读代码的人都无法判断它是一个虚函数,这可能会造成灾难性的误导!但是,您可以安全地使用 virtual 关键字:

    虚拟整数 MyFunc()
    这使任何阅读您的类头文件的人都清楚该方法是虚拟的。(这个“C++ 语法错误”在 C# 中通过在这种情况下需要使用“override”关键字来修复 - 如果有人需要它,更多的证据表明错过“不必要的虚拟”是一个非常糟糕的主意)

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

于 2009-10-16T19:19:51.523 回答