main() 如何接收命令行参数?
Posted
技术标签:
【中文标题】main() 如何接收命令行参数?【英文标题】:How does main() receive command line arguments? 【发布时间】:2022-01-10 07:32:13 【问题描述】:使用标准 C++ 条目:
int main(int argc, char *argv[])
// stuff
argv
是如何填充的?编译器不知道为数组分配什么大小,我假设操作系统是负责将附加参数传递给程序的实体,但它们如何传递给main
?指针数组在哪里初始化?该函数是由编译器创建然后注入程序启动序列的吗?
这是我一直认为理所当然的事情,今天我开始思考一个问题,我不确定main
最终如何收到额外的参数,更不用说给予任何程序,例如 CPython 中的 sys.argv
。
奖励:操作系统如何处理命令行参数?显然 CLI(或 shell)知道如何解析字符串序列,但是附加参数是如何“输入”到可执行文件中的呢?编译器是否添加了一些功能以仅从stdin
(这是一个缓冲区)读取并在传递给main
之前相应地解析参数?
【问题讨论】:
相关:How do command line arguments work? 当我在一个大学项目中为一个玩具操作系统实现这个功能时,只需 mallocing() 一个足够大的内存块来保存指针数组和它需要的字符串指向,然后复制字符串数据并将指针设置为指向它。我敢肯定真正的操作系统会做一些更精细的事情,但这不是火箭科学。 @JeremyFriesner 这可能会回答操作系统如何解析命令行并存储它们,但它没有回答操作系统如何将它们注入二进制可执行文件以传递给main
。我的猜测是操作系统运行可执行文件,它将程序输出到内存中,然后它可以访问main
以将数组传递给它。
@madeslurpy 操作系统不会修改可执行文件;但它确实设置了可执行文件将运行的内存空间。在该内存空间内分配和填充 argv 数组是该设置的一部分,该设置在调用 main()
之前完成。
顺便说一句,不要让char *[]
语法欺骗你,它在逻辑上等同于char **
,即参数只是一个指针,因此它指向的数组的大小不必在编译时就知道了。
【参考方案1】:
我们以 Linux x86-64 为例。
当进程调用execv("/my/prog", args)
时,它会对内核进行系统调用。内核使用args
指针在进程内存中定位参数字符串,将它们复制到其他地方以临时保管,然后拆除进程的虚拟内存。然后它为新程序设置虚拟内存,并从其二进制文件/new/prog
加载其代码和数据(实际上它只是将其映射为按需加载,但这并不重要)。
它还分配一块内存作为新程序的堆栈,在那里它复制命令行参数、环境变量和需要传递给新程序的各种其他数据。在这里,它还设置了argv
指针数组,指向程序堆栈内存中的字符串本身,并将参数计数也压入堆栈。具体布局在the ABI中指定,见图3.9。
现在开始实际启动程序。二进制文件的标头指定用作入口点的地址。链接器将安排 this 指向一段特殊的启动代码。此代码通常随您的标准 C 库一起提供,位于名称类似于 crt0.o
的目标文件中。它是用汇编语言编写的,它的工作是处理命令行参数等,按照编译的 C 或 C++ 代码所期望的方式设置寄存器和内存,并调用标准库中的 C/C++ 函数。进一步初始化,然后致电您的main
。内核跳转到入口点地址,一路切换到非特权模式,启动代码开始执行。
您可以在start.S
中看到 glibc 的版本,但非常小的版本可能看起来像这样。
; main takes argc in rdi and argv in rsi
; bottom of stack contains argument count
mov rdi, [rsp]
; next is start of the argument pointer array
lea rsi, [rsp+8]
call main
; main returns, exit the program
mov rdi, rax
call exit
; exit() makes an exit system call and doesn't return
因此,当控制权实际到达您的main
函数时,寄存器包含的值与其他 C++ 函数调用的值相同。 argv
参数指向堆栈上的一个指针数组,每个指针都指向一个位于堆栈内存中更靠前的字符串,由内核设置。
【讨论】:
【参考方案2】:argv 是如何填充的?
语言实现(我的意思是除此之外的一切,例如 shell、操作系统等)负责处理它。
我假设操作系统是负责将附加参数传递给程序的实体
差不多,是的。
指针数组在哪里初始化?
语言实现选择初始化它们的地方。
奖励:操作系统如何处理命令行参数?
没有一个操作系统。有很多,每个人都做自己的事情。其中一些是开源的,因此您可以研究它们。
【讨论】:
假设如下:语言实现是 C++(无论标准是什么),操作系统是 Windows。以“他们都以自己的方式,不知何故”的形式给出的答案是非常模糊的,即使是有效的,任何组合的标准事件序列的工作示例是有启发性的。否则就像是在说“电流流动只是因为它的介质是如何工作的”。 @madeslurpy 我不知道 windows 是如何工作的,因为我没有源代码,所以我无法找到。is extremely vague
对应问题的模糊性。
不幸的是,你运气不好。 MS Windows 是一个商业操作系统,它的大部分源代码作为专有信息被锁起来。这方面的文档很有可能被埋在 MSDN 中的某个地方,也许其中的一部分需要花费真正的美元来购买访问权限。或者,Microsoft 可能根本不会记录它。由于为 MS Windows 编写工作软件不需要这样的东西,因此微软没有太多理由为其发布任何公共文档。
您可能不了解 Windows,但您肯定知道它们如何协同工作的某种组合。如果是这样,为什么不分享呢?为了不那么迂腐,让我们以 Unix 作为操作系统?
@madeslurpy 大多数Nate's answer 在 Windows 上也类似。主要区别在于Windows创建新进程时,命令行被复制到进程的PEB
头文件中,然后程序的运行库的启动代码通过GetCommandLineA()
检索命令行并拆分将其放入传递给main()
函数的char[]
数组以上是关于main() 如何接收命令行参数?的主要内容,如果未能解决你的问题,请参考以下文章