我的应用程序可能传入了大量的参数,我想避免将参数复制到过滤列表中的命中记忆。我想将它们过滤到位,但我很确定弄乱 argv 数组本身或它指向的任何数据可能是不可取的。有什么建议么?
8 回答
C99 标准说明了关于修改argv
(and argc
) 的内容:
参数 argc 和 argv 以及 argv 数组指向的字符串应该可以被程序修改,并在程序启动和程序终止之间保留它们最后存储的值。
一旦 argv 被传递到 main 方法中,您就可以像对待任何其他 C 数组一样对待它 - 随意更改它,只需注意您正在使用它做什么。除了您在代码中明确使用它执行的操作之外,数组的内容不会影响程序的返回代码或执行。我想不出任何理由“不建议”特别对待它。
当然,您仍然需要注意意外访问超出 argv 范围的内存。它像普通 C 数组一样可访问的另一面是,它也像任何其他普通 C 数组一样容易出现访问错误。(感谢所有在评论和其他回复中指出这一点的人!)
最新的 C 标准草案 (N1256) 规定该main
函数有两种允许的形式:
int main (void);
int main (int argc, char* argv[]);
但关键是“或以其他一些实现定义的方式”的子句。在我看来,这似乎是标准中的一个漏洞,大到足以让一辆半挂车通过。
有些人专门使用"const char *"
来argv
禁止更改参数。如果您的 main 函数以这种方式定义,则不允许更改argv[]
指向的字符,如以下程序所示:
pax> cat qq.c
#include <stdio.h>
int main (int c, const char *v[]) {
*v[1] = 'X';
printf ("[%s]\n", v[1]);
return 0;
}
pax> gcc -o qq qq.c
qq.c: In function `main':
qq.c:3: error: assignment of read-only location
但是,如果您删除"const"
,它可以正常工作:
pax> cat qq2.c
#include <stdio.h>
int main (int c, char *v[]) {
*v[1] = 'X';
printf ("[%s]\n", v[1]);
return 0;
}
pax> gcc -o qq2 qq2.c ; ./qq2
[Xello]
我认为这也是 C++ 的情况。目前的草案规定:
All implementations shall allow both of the following definitions of main:
int main();
int main(int argc, char* argv[]);
但它并没有明确禁止其他变体,因此您可能也可以接受"const"
C++ 中的版本(事实上,g++ 可以)。
您唯一需要注意的是尝试增加任何元素的大小。这些标准没有规定它们的存储方式,因此扩展一个参数可能(可能会)影响其他参数或其他一些不相关的数据。
根据经验,诸如 GNU getopt() 之类的函数可以置换参数列表而不会引起问题。正如@Tim 所说,只要您明智地玩,就可以操纵指针数组,甚至可以操纵单个字符串。只是不要超出任何隐式数组边界。
操作系统在执行之前将 argv 和 argc 推送到应用程序堆栈中,您可以像对待任何其他堆栈变量一样对待它们。
一些图书馆这样做!
由 glut opengl 库(GlutInit)提供的初始化方法扫描 glut 相关参数,并通过向前移动后续元素argv
(移动指针,而不是实际字符串)和递减 argc来清除它们
2.1
glutInit glutInit 用于初始化 GLUT 库。
用法
void glutInit(int *argcp, char **argv);
argcp
从 main 指向程序未修改的 argc 变量的指针。返回时, argcp 指向的值将被更新,因为 glutInit 提取任何用于 GLUT 库的命令行选项。
argv
程序未修改的来自 main 的 argv 变量。与 argcp 一样,argv 的数据将被更新,因为 glutInit 提取 GLUT 库可以理解的任何命令行选项。
唯一一次我会说直接操作 argv 是一个坏主意是当应用程序根据 argv[0] 的内容改变其行为时。
但是,在考虑可移植性的情况下,根据 argv[0] 更改程序的行为本身就是一个非常糟糕的主意。
除此之外,您可以像对待任何其他阵列一样对待它。正如乔纳森所说,GNU getopt() 非破坏性地置换参数列表,我已经看到其他 getopt() 实现可以序列化甚至散列参数(当程序接近 ARG_MAX 时很有用)。
小心你的指针算术。
的原始分配argv
留作编译器/运行时选择。因此,随意修改它可能不安全。许多系统在堆栈上构建它,因此当 main 返回时它会自动释放。其他人在堆上构建它,并在 main 返回时释放它(或不释放它)。
只要您不尝试使其更长(缓冲区溢出错误),更改参数的值是安全的。打乱参数的顺序是安全的。
要删除您已预处理的参数,可以使用以下方法:
(许多错误条件未检查,“--special”其他未检查第一个 arg 等。这毕竟只是一个概念演示。)
int main(int argc, char** argv)
{
bool doSpecial = false; // an assumption
if (0 == strcmp(argv[1], "--special"))
{
doSpecial = true; // no longer an assumption
// remove the "--special" argument
// but do copy the NULL at the end.
for(int i=1; i<argc; ++i)
argv[i] = argv[i+1];
--argc;
}
// all normal processing with "--special" removed.
// the doSpecial flag is available if wanted.
return 0;
}
但是请参阅此进行完整操作:(用于操作 argv 样式向量的libiberty库的一部分)
http://www.opensource.apple.com/source/gcc/gcc-5666.3/libiberty/argv.c
它已获得 GNU LGPL 许可。