Menu

C 语言命令行参数:argc、argv 与安全解析

C 程序如何读取命令行:argc 和 argv 的构成、遍历参数、argv[0] 里存的是什么、用 strtol 而不是 atoi 转换数字,以及一个把这些串起来的小计算器。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

你在终端里运行的每个程序都可以接收参数:gcc -Wall hello.c -o hello 就传了四个。你自己写的 C 程序通过 main 的两个参数读取它们,整个机制就这么简单:不需要调用库函数,也不需要任何设置。

函数签名

到目前为止,这些文档中的每个例子都用的是 int main(void)。另一种标准形式会接收命令行:

int main(int argc, char *argv[]) {
    /* ... */
}
  • argc(argument count,参数个数)表示有多少个参数,包括程序名。
  • argv(argument vector,参数向量)是一个字符串数组。char *argv[] 的意思是“指向 char 的指针数组”,也就是由 C 字符串组成的数组。

你也会经常看到等价的写法 char **argv;数组形参会退化为指针,所以这两种写法声明的是同一个东西。

用编辑器的 参数 面板提供参数(每个输入框是一个参数),然后点击运行。试试填三个:hello、world、42。

参数的构成

运行 ./greet Ada Lovelace 会得到:

下标值
argv[0]"./greet"调用时使用的程序名
argv[1]"Ada"第一个真正的参数
argv[2]"Lovelace"第二个真正的参数
argv[3]NULL结束标记
argc3个数,包括 argv[0]

值得记住的四个事实:

  • 实际使用中,argc 至少为 1,因为 argv[0] 是程序名。真正的参数从下标 1 开始,所以遍历参数的循环从 i = 1 开始。
  • 一切都是字符串。 ./prog 42 给你的是 "42" 这两个字符,而不是数字 42。转换是你自己的工作。
  • argv[argc] 是 NULL。 这由标准保证,所以如果你愿意,也可以不用 argc 来遍历数组:for (char **p = argv + 1; *p != NULL; p++)。
  • 拆分单词的是 shell,不是你的程序。 ./prog "Ada Lovelace" 是一个参数;引号被 shell 吃掉了,永远不会出现在 argv 里。

读取之前先检查

没有传入的参数并不是空字符串,而是一块你无权访问的内存。一定要先验证 argc:

这个小程序里有两个约定,在 Unix 工具中都是标准做法:

  • 用法提示输出到 stderr,而不是 stdout,这样就不会污染通过管道传给其他程序的输出。
  • main 返回非零值,向运行它的程序表示失败。0 表示成功,其他任何值都表示出了问题。Shell 脚本会检查这个值。

打印 argv[0] 而不是写死程序名,可以让提示信息与用户的实际调用方式一致,即使程序被改了名或通过不同路径调用也一样。

转换数字参数

argv[1] 是文本。要做算术运算就必须先转换,应该使用的函数是 <stdlib.h> 中的 strtol:

在参数面板中添加 10、20、30 三个参数,然后把其中一个换成 banana,看看出错时的处理。

strtol 的第三个参数是进制:10 表示十进制,16 表示十六进制,0 表示根据 0x 或前导零前缀自动识别。第二个参数用来存放一个指针,指向它没有处理的第一个字符,正是这一点让错误检查成为可能:

  • end == text 表示什么都没解析到:参数不是以数字开头的。
  • *end != '\0' 表示数字后面还有剩余文本,所以 "12abc" 会被拒绝,而不是悄悄变成 12。
  • errno == ERANGE 表示值超出了 long 的范围。

对比一下 atoi,它更短,但这些情况一个都报告不了:

int n = atoi(argv[1]);   /* "banana" -> 0, "0" -> 0, overflow -> undefined */

atoi 对无效输入返回 0,这和真正的零无法区分。只要输入来自程序外部,就用 strtol(浮点数用 strtod)。更多内容见字符串转换。

一个小计算器

把这些组合起来:一个接收数字、运算符、数字的程序:

试试 12 + 30,然后是 7 x 6,再试 5 / 0。

注意这里写的是 strcmp(op, "+") == 0,而不是 op == "+"。用 == 比较 char * 值比较的是地址而不是内容,即使文本完全相同,结果也会是假。参见字符串函数。

用 x 代替 * 是一个来自实际使用的细节:大多数 shell 会在你的程序运行之前,把单独的 * 展开成当前目录下的文件列表。给它加上引号('*')也行,但选一个 shell 不会处理的字符更友好。

解析选项

按照惯例,选项以 - 开头。一个简单的手写循环就能处理常见情况:

用四个参数试试:-v、-n、3、hello。

argv[++i] 之前的 i + 1 >= argc 检查是最关键的一行:需要值的选项必须确认这个值真的存在,否则当 -n 是最后一个参数时,程序就会读到数组末尾之外。在真正的 Unix 系统上,<unistd.h> 中的 getopt 会帮你完成这一切,包括 -vn3 这样合并在一起的短选项;一旦程序的选项超过两三个,就值得用它。

浏览器编辑器中的参数

本页的编辑器代码块会为你编译并运行,参数 面板就是你填写终端中程序名后面那些内容的地方。你用 添加参数 加上的每个输入框,正好对应 argv 中的一项。中间没有 shell,所以不会按空格拆分,也不需要引号:内容为 hello world 的输入框会作为单个参数 hello world 传入。要传 -n 3 hello,就添加三个输入框。面板留空时 argc == 1,这就是为什么第一次运行时 argc < 2 的检查会触发,也是它值得写上的原因。

常见错误

  • 不检查 argc 就读取 argv[1]。 没有传入参数时会导致未定义行为,这也是这方面最常见的崩溃原因。
  • 循环从 i = 0 开始。 这样会把程序名当成数据处理。
  • 把 argv[1] 当成数字。 在你转换之前,它都是文本。
  • 对用户输入使用 atoi。 无法区分错误的参数和零。
  • 用 == 比较字符串。 请用 strcmp。
  • 往 argv 的字符串里写入。 标准允许修改它们,但空间有限且依赖平台;如果需要修改,就复制到你自己的缓冲区里。

常见问题

C 语言中 argc 和 argv 是什么?

它们是 int main(int argc, char *argv[]) 的两个参数。argc 是命令行参数的个数,包括程序名;argv 是由这些参数组成的字符串数组。argv[0] 是程序名,argv[1] 是第一个真正的参数,而 argv[argc] 始终是 NULL。

怎么给 C 程序传递参数?

在终端里把参数写在可执行文件名后面:./program hello 42。这时 argc == 3,argv[1] 是字符串 "hello",argv[2] 是字符串 "42"。在本页的编辑器中,参数填在“参数”面板里,而不是终端中。

C 语言怎么把命令行参数转换成 int?

使用 strtol:char *end; long n = strtol(argv[1], &end, 10);,然后检查 end 是否仍指向开头(什么都没解析到),以及 *end 是否为 '\0'(后面没有多余的字符)。atoi 更短,但对 "0" 和 "banana" 都返回 0,所以它无法报告错误。

C 语言中 argv[0] 是什么?

启动程序时使用的名字,通常是 ./program 或完整路径。它在用法提示中很有用(fprintf(stderr, "usage: %s FILE\n", argv[0])),能让提示与用户实际的调用方式一致。在少数特殊情况下它可能是空字符串,所以不要假定它一定存在。

Coddy 编程语言插图

和 Coddy 一起学编程

开始