考虑以下代码:
#include <stdio.h>
#include <unistd.h>
void foo(char * const arg[]) {
printf("success\n");
}
int main() {
char myargs[2][64] = { "/bin/ls", NULL };
foo(myargs);
execv(myargs[0], myargs);
return 0;
}
foo
和execv
都需要char * const *
参数,但是当我的foo
工作(输出中得到success
)时,系统调用{{1 }}失败。
我想知道为什么。这与execv
的实现有关吗?
还要假设我有一个execv
变量-如何将其发送到char**
?
答案 0 :(得分:3)
一个二维数组看起来像这样:
char myargs[2][16];
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| | | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| | | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
我将尺寸从64减小到16,以防止该图变得烦人。
使用初始化程序,它看起来可能像这样:
char myargs[2][16] = { "/bin/ls", "" }
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| /| b| i| n| /| l| s|\0| | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|\0| | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
注意,我没有尝试在第二行中放置空指针。这样做是没有意义的,因为那是一个字符数组。指针中没有位置。
行在内存中是连续的,因此,如果您查看较低的级别,则实际上更像是这样:
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| /| b| i| n| /| l| s|\0| | | | | | | | |\0| | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
当您将myargs
传递给函数时,著名的“数组衰减”将产生一个指针。看起来像这样:
void foo(char (*arg)[16]);
...
char myargs[2][16] = { "/bin/ls", "" }
foo(myargs);
+-----------+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| POINTER==|===>| /| b| i| n| /| l| s|\0| | | | | | | | |\0| | | | | | | | | | | | | | | |
+-----------+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
指针arg
包含一个用于定位数组开头的值。注意,没有指针指向第二行。如果foo
要在第二行中查找值,则需要知道行有多大,以便可以对此进行分解:
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| /| b| i| n| /| l| s|\0| | | | | | | | |\0| | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
对此:
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| /| b| i| n| /| l| s|\0| | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|\0| | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
这就是为什么arg
必须是char (*arg)[16]
而不是char **arg
或等效的char *arg[]
的原因。
exec
函数系列不适用于此数据布局。它想要这样:
+-----------+ +-----------+-----------+
| POINTER==|===>| POINTER | NULL |
+-----------+ +-----|-----+-----------+
|
/----------------------/
|
|
| +--+--+--+--+--+--+--+--+
\--->| /| b| i| n| /| l| s|\0|
+--+--+--+--+--+--+--+--+
当您想添加更多参数时,它会这样:
+-----------+ +-----------+-----------+- -+-----------+
| POINTER==|===>| POINTER | POINTER | ... | NULL |
+-----------+ +-----|-----+-----|-----+- -+-----------+
| |
/----------------------/ |
| |
| /--------------------------------/
| |
| |
| | +--+--+--+--+--+--+--+--+
\-+->| /| b| i| n| /| l| s|\0|
| +--+--+--+--+--+--+--+--+
|
| +--+--+--+--+--+--+
\->| /| h| o| m| e|\0|
+--+--+--+--+--+--+
如果将此与二维数组图进行比较,希望您可以理解为什么它不能是隐式转换。实际上,这涉及到在内存中移动内容。
答案 1 :(得分:2)
foo
和execv
都需要char * const *
参数,
是的
但是当我的
foo
工作正常(输出成功)时,系统调用execv
失败了。
获得期望的输出并不能证明您的代码正确。该调用表现出未定义的行为,因为其参数与参数类型不匹配,但似乎没有什么实际效果,因为foo()
的实现未以任何方式使用该参数。更一般而言,原则上,您的代码完全可以表现出任何行为,因为这就是“未定义”的含义。
我想知道为什么。这与
execv
的实现有关吗?
从标准的角度来看,两个调用都表现出同样未定义的行为。但是,实际上,我们知道execv
确实使用了它的参数,因此,对于该调用产生您期望的行为,比对foo
产生调用的行为更令人惊讶您预期的行为。
主要问题是2D arrays are arrays of arrays和arrays are not pointers。因此,您的2D数组myargs
对于任何一个函数的参数都根本没有正确的类型。
此外,假设我有一个char **变量-如何将其发送到execv?
您的代码中没有这样的变量,但是如果您有,可以将其强制转换为适当的类型:
char *some_args[] = { "/bin/ls", NULL };
execv((char * const *) some_args);
在实践中,即使标准也要求强制转换,但即使省略了强制转换,大多数编译器也可能会接受它。最好是首先声明一个具有正确类型的变量:
char * const correct_args[] = { "/bin/ls", NULL };
execv(correct_args);
还请注意,尽管数组不是指针,但它们在大多数上下文中都会被转换为指针(我在示例代码中使用过),但它们只是顶层。因此,数组数组会“衰减”到指向数组的指针,而不是指向指针的指针。