为什么argc不是常量?

108
int main( const int argc , const char[] const argv)

作为Effective C++第3条所述“尽可能使用const”,我开始思考“为什么不将这些‘常量’参数设置为const”?
在程序中是否存在修改argc值的情况?

40
你可以将 argc 声明为 const - Oliver Charlesworth
14
我已经看到许多生产质量的代码使用这种方式:--argc - Aniket Inge
2
我认为这可能与C语言的遗留问题有关,但我不知道具体是什么。 - Luis Machuca
14
我猜“尽可能使用const”是指通过引用传递的东西,在函数内的任何修改在函数退出后仍然存在。对于通过值传递的东西,比如整数,这种情况并不适用,所以将其声明为“const”没有任何好处;实际上,将“argc”声明为“const int”意味着您无法在函数内部将其用作计数器等变量。 - Steve Melnikoff
4
我不同意 const 作为传值参数并不能获得任何好处的说法。可以参考以下链接:https://dev59.com/Bmoy5IYBdhLWcg3wMLOj#8714278 和 https://dev59.com/iXVD5IYBdhLWcg3wBWvd#117557。 - leonbloy
显示剩余3条评论
7个回答

118
在这种情况下,历史是一个因素。C将这些输入定义为“非常量”,与(很大一部分)现有的C代码兼容是C++的早期目标之一。
一些UNIX API,例如getopt,实际上确实操作argv[],所以它也不能被设置为const
(顺便说一下:有趣的是,尽管getopt的原型表明它不会修改argv[]但可能会修改指向的字符串,但Linux手册指出getopt重新排列其参数,并且他们似乎知道自己在做坏事。 Open Group的手册没有提到这种排列。)
argcargv上放置const并没有多大好处,而且会使一些老派编程实践失效,例如:
// print out all the arguments:
while (--argc)
    std::cout << *++argv << std::endl;

我已经用C语言编写了这样的程序,我知道我不是唯一的。我从某个地方复制了示例。


1
-1 关于“某些UNIX API,例如getopt,实际上确实操作argv[],因此不能为此原因使其成为const。” 人们可以自由地将顶层的const添加到argv中,而不会影响任何C函数所能做的事情。此外,getopt声明为int getopt(int argc, char * const argv[], const char *optstring);。在这里,“const”不是顶层的,但是它声明指针是const,承诺不修改它们(尽管它们指向的字符串可能会被修改)。 - Cheers and hth. - Alf
2
很抱歉,我查看的文档(http://man7.org/linux/man-pages/man3/getopt.3.html)不一致:签名不允许这种置换。请参见示例(http://coliru.stacked-crooked.com/a/6fcab492c134b11c)。然而下面的文本说它确实会置换。所以,我撤销了反对票。 - Cheers and hth. - Alf
1
你需要进行一些编辑才能“解锁”,这样我才能取消踩。不,你误解了原型。看看我提供的示例和编译错误,“分配只读位置”。 - Cheers and hth. - Alf
1
@Cheersandhth.-Alf:很有趣...我查了一下头文件中的原型,它有char* const* ___argv,但实际接口遵循const char **。这样很容易混淆。我混淆了[]*相对于const的优先级。请原谅我的错误。 - Joe Z
1
FYI:GNU getopt() 知道它不遵循标准,因为它会注意 POSIXLY_CORRECT 环境变量以使其正确地运行。特别是,POSIX 不会对参数进行置换,并且不会在第一个非选项参数之后查找选项。 - Jonathan Leffler
显示剩余5条评论

38

C标准(ISO/IEC 9899:2011)如下所述:

5.1.2.2.1程序启动

¶1 在程序启动时调用的函数名为main。实现不声明此函数的原型。它应该定义为具有整数返回类型和没有参数的函数:

int main(void) { /* ... */ }

或者使用两个参数(这里被称为argcargv,尽管可以使用任何名称,因为它们是在声明它们的函数中本地的):

int main(int argc, char *argv[]) { /* ... */ }

10)或以其他实现定义的方式。

¶2 如果它们被声明,main函数的参数应遵守以下约束:

  • argc的值必须为非负数。
  • argv[argc] 必须是空指针。
  • 如果 argc 的值大于零,则数组成员 argv[0]argv[argc-1] (含)必须包含指向字符串的指针,这些指针在程序启动前由主机环境赋予实现定义的值。目的是为了向程序提供从托管环境中其他位置确定的信息。如果主机环境无法提供同时具有大写和小写字母的字符串,则实现必须保证字符串以小写形式接收。
  • 如果 argc 的值大于零,则由 argv[0] 指向的字符串表示程序名称;如果程序名称不可从主机环境获得,则 argv[0][0] 必须是空字符。如果 argc 的值大于 1,则由 argv[1]argv[argc-1] 指向的字符串表示程序参数。
  • 参数 argcargv 以及 argv 数组指向的字符串必须可由程序修改,并在程序启动和程序终止之间保留它们的最后存储值。

10)因此,int 可以被定义为 int 的 typedef 名称替换,或者将 argv 的类型写成 char **argv 等等。

请注意最后一个要点。它说 argcargv 都应该是可修改的。它们不一定需要被修改,但可能会被修改。


有趣。半相关的是,我遇到了一个导致崩溃的错误,原因是Qt的QApplication(argc,argv)构造函数被定义为引用argc(http://qt-project.org/doc/qt-5.0/qtwidgets/qapplication.html#QApplication)。这让我感到惊讶。 - HostileFork says dont trust SE

24

argc通常不是一个常量,因为main()函数的签名早于const

由于argc是一个堆栈变量,改变它不会影响除了你自己的命令行处理以外的任何东西。

当然,如果您愿意,可以将其声明为const


8

在形式参数上使用顶层 const 不是函数类型的一部分。您可以随意添加或删除它:它只影响函数实现中对参数的使用。

因此,对于 argc,您可以自由添加 const

但是对于 argv,如果使字符数据为 const,就会改变函数签名。这意味着它不是标准的 main 函数签名之一,并且将不必被识别为 main 函数。所以,这不是一个好主意。


不使用标准的 main 参数的一个好理由是,在 Windows 中,它们无法表示带有国际字符文件名等实际程序参数。这是因为在 Windows 中,它们按照非常强的惯例编码为 Windows ANSI。在 Windows 中,您可以通过 GetCommandLine API 函数实现更可移植的参数访问功能。


总之,没有什么阻止您向 argc 添加 const,但在 argv 上使用最有用的 const 将给您提供一个非标准的 main 函数,很可能不会被识别为这样的函数。令人欣慰的是(以一种讽刺的方式),有很好的理由不使用标准的 main 参数来编写可移植的严肃代码。简单地说,它们只支持旧的 ASCII,只有英文字母。


1
如果您正在使用Cygwin或其他正确支持UTF-8的libc进行Windows开发(据我所知,目前只有Cygwin),则标准的main签名可以正常工作,并且您可以接收任意Unicode参数。 - R.. GitHub STOP HELPING ICE
@R 它们在大多数*nix平台上也可以很好地工作。问题不在于它们能否在可用的平台上工作,而是这是一个非常好的倡议,正好我也在做同样的事情,或多或少地认真对待。 - Cheers and hth. - Alf

4
的签名有些深远的历史意义,它来自于C语言。在C语言历史上不存在const
然而,你可以将参数声明为const,因为const的作用仅限于编译时。


当你说“历史上的C语言”时,我们在谈论多久以前的历史? - Joe Z
8
在 C89/C90 中加入了 const 关键字,之前它并不属于 C 的一部分。 - Jonathan Leffler
@JonathanLeffler:你知道吗,我已经忘记了。我是在1992年学习C语言的。在那之前,我是一个Pascal、BASIC和汇编语言的黑客。 - Joe Z

2

因为argc是一个局部变量(在C++中不是引用或其他东西),并且由于main的特殊地位意味着向后兼容性的花招赋予它巨大的自由度,而没有强制应用程序将其设置为const的必要性。

main() {}

int main() {}

main() { return 0; }

main(int argc, char* argv[]) { return 0; }

int main(const int argc, const char** argv) { /* no return*/ }

这些以及其他许多变体都可以在广泛的C和C++编译器上编译。

因此,归根结底,argc并非不是const,只是它不必是,但如果您想使其成为const,那么它可以是。

http://ideone.com/FKldHF,C示例:

main(const int argc, const char* argv[]) { return 0; }

http://ideone.com/m1qc9c,这是一个C++示例。

main(const int argc) {}

请注意,C99甚至C11中不再支持没有显式返回类型的变量,尽管编译器出于向后兼容性的原因仍然允许使用。C++编译器不应接受没有返回类型的变量。 - Jonathan Leffler
@JonathanLeffler 是的,但是那里的最后一个ideone示例(http://ideone.com/m1qc9c)是带有`-std=c++11`的G++ 4.8.2。Clang也接受它,但会给出“warning:only one parameter on 'main' declaration [-Wmain]”的警告,而MSVC则只抗议缺少返回类型说明符和对argc的引用。再次,这是“诡计”和“余地” :) - kfsone

1
除了历史原因外,保持argc和argv非const的好理由是编译器实现不知道您将对main函数的参数执行什么操作,它只知道必须给您这些参数。
当您定义自己的函数及其相关原型时,您知道哪些参数可以使const,哪些参数会被您的函数修改。
把这个想法推到极致,您可以声明所有函数的所有参数都应该声明为const,然后如果您有理由更改它们(例如递减索引以搜索数组),您就必须创建本地非const变量,并将const参数的值复制到这些变量中。这会导致繁琐的工作和额外的LOC,没有真正的好处。一个不错的静态分析器会发现您是否未修改参数的值,并建议您将参数声明为const

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接