为什么 `execv` 不能使用从 char** 到 char* const* 的隐式转换?
why `execv` can't use implicit convert from char** to char* const*?
考虑以下代码:
#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
)系统调用 execv
失败。
我想知道为什么。这和execv
的实现有关系吗?
此外,假设我有一个 char**
变量 - 我如何将它发送到 execv
?
二维数组如下所示:
char myargs[2][16];
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| | | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| | | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
我将大小从 64 减少到 16,以免图表大得令人讨厌。
有了初始化器,它看起来像这样:
char myargs[2][16] = { "/bin/ls", "" }
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| /| b| i| n| /| l| s|[=11=]| | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|[=11=]| | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
请注意,我没有尝试在第二行中放置一个空指针。这样做没有意义,因为那是一个字符数组。里面没有放指针的地方。
行在内存中是连续的,所以如果往下看,其实更像这样:
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| /| b| i| n| /| l| s|[=12=]| | | | | | | | |[=12=]| | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
当您将 myargs
传递给函数时,著名的 "array decay" 会生成一个指针。看起来像这样:
void foo(char (*arg)[16]);
...
char myargs[2][16] = { "/bin/ls", "" }
foo(myargs);
+-----------+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| POINTER==|===>| /| b| i| n| /| l| s|[=13=]| | | | | | | | |[=13=]| | | | | | | | | | | | | | | |
+-----------+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
指针 arg
包含一个位于数组开头的值。请注意,没有指针指向第二行。如果 foo
想在第二行中找到值,它需要知道行有多大才能分解:
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| /| b| i| n| /| l| s|[=12=]| | | | | | | | |[=12=]| | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
进入这个:
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| /| b| i| n| /| l| s|[=15=]| | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|[=15=]| | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
这就是为什么 arg
必须是 char (*arg)[16]
而不是 char **arg
或等价的 char *arg[]
。
exec
系列函数不适用于此数据布局。它想要这个:
+-----------+ +-----------+-----------+
| POINTER==|===>| POINTER | NULL |
+-----------+ +-----|-----+-----------+
|
/----------------------/
|
|
| +--+--+--+--+--+--+--+--+
\--->| /| b| i| n| /| l| s|[=16=]|
+--+--+--+--+--+--+--+--+
当你想添加更多参数时,它需要这样:
+-----------+ +-----------+-----------+- -+-----------+
| POINTER==|===>| POINTER | POINTER | ... | NULL |
+-----------+ +-----|-----+-----|-----+- -+-----------+
| |
/----------------------/ |
| |
| /--------------------------------/
| |
| |
| | +--+--+--+--+--+--+--+--+
\-+->| /| b| i| n| /| l| s|[=17=]|
| +--+--+--+--+--+--+--+--+
|
| +--+--+--+--+--+--+
\->| /| h| o| m| e|[=17=]|
+--+--+--+--+--+--+
如果将此与二维数组图进行比较,希望您能理解为什么这不能是隐式转换。它实际上涉及在内存中移动内容。
Both foo
and execv
require char * const *
argument,
是的。
but while my foo
works (I get success in the output) the system call execv
fails.
得到您期望的输出并不能证明您的代码是正确的。该调用表现出未定义的行为,因为它的参数与参数类型不匹配,但这似乎没有什么实际效果,因为 foo()
的实现不以任何方式使用该参数。更一般地说,原则上,您的代码完全可以表现出任何行为,因为这就是 "undefined" 的意思。
I would like to know why. Does this have something to do with the implementation of execv
?
从标准的角度来看,这两个调用都表现出同样未定义的行为。然而,作为一个实际问题,我们知道 execv
确实使用了它的参数,因此与调用 foo
产生您预期的行为相比,该调用产生您预期的行为要令人惊讶得多您期望的行为。
主要问题是2D arrays are arrays of arrays, and arrays are not pointers。因此,您的二维数组 myargs
对于任一函数的参数都没有正确的类型。
Also, assuming I have a char** variable - how can I send it to execv?
您的代码中没有这样的变量,但如果有,您可以将其转换为适当的类型:
char *some_args[] = { "/bin/ls", NULL };
execv((char * const *) some_args);
实际上,如果您也省略强制转换,大多数编译器可能会接受它,尽管标准确实需要它。最好首先声明一个具有正确类型的变量:
char * const correct_args[] = { "/bin/ls", NULL };
execv(correct_args);
另请注意,虽然数组不是指针,但它们在大多数上下文中转换 为指针——我在示例代码中使用的——但仅限于顶层。因此,数组的数组 "decays" 指向指向数组的指针,而不是指向指针的指针。
考虑以下代码:
#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
)系统调用 execv
失败。
我想知道为什么。这和execv
的实现有关系吗?
此外,假设我有一个 char**
变量 - 我如何将它发送到 execv
?
二维数组如下所示:
char myargs[2][16];
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| | | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| | | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
我将大小从 64 减少到 16,以免图表大得令人讨厌。
有了初始化器,它看起来像这样:
char myargs[2][16] = { "/bin/ls", "" }
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| /| b| i| n| /| l| s|[=11=]| | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|[=11=]| | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
请注意,我没有尝试在第二行中放置一个空指针。这样做没有意义,因为那是一个字符数组。里面没有放指针的地方。
行在内存中是连续的,所以如果往下看,其实更像这样:
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| /| b| i| n| /| l| s|[=12=]| | | | | | | | |[=12=]| | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
当您将 myargs
传递给函数时,著名的 "array decay" 会生成一个指针。看起来像这样:
void foo(char (*arg)[16]);
...
char myargs[2][16] = { "/bin/ls", "" }
foo(myargs);
+-----------+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| POINTER==|===>| /| b| i| n| /| l| s|[=13=]| | | | | | | | |[=13=]| | | | | | | | | | | | | | | |
+-----------+ +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
指针 arg
包含一个位于数组开头的值。请注意,没有指针指向第二行。如果 foo
想在第二行中找到值,它需要知道行有多大才能分解:
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| /| b| i| n| /| l| s|[=12=]| | | | | | | | |[=12=]| | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
进入这个:
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| /| b| i| n| /| l| s|[=15=]| | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|[=15=]| | | | | | | | | | | | | | | |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
这就是为什么 arg
必须是 char (*arg)[16]
而不是 char **arg
或等价的 char *arg[]
。
exec
系列函数不适用于此数据布局。它想要这个:
+-----------+ +-----------+-----------+
| POINTER==|===>| POINTER | NULL |
+-----------+ +-----|-----+-----------+
|
/----------------------/
|
|
| +--+--+--+--+--+--+--+--+
\--->| /| b| i| n| /| l| s|[=16=]|
+--+--+--+--+--+--+--+--+
当你想添加更多参数时,它需要这样:
+-----------+ +-----------+-----------+- -+-----------+
| POINTER==|===>| POINTER | POINTER | ... | NULL |
+-----------+ +-----|-----+-----|-----+- -+-----------+
| |
/----------------------/ |
| |
| /--------------------------------/
| |
| |
| | +--+--+--+--+--+--+--+--+
\-+->| /| b| i| n| /| l| s|[=17=]|
| +--+--+--+--+--+--+--+--+
|
| +--+--+--+--+--+--+
\->| /| h| o| m| e|[=17=]|
+--+--+--+--+--+--+
如果将此与二维数组图进行比较,希望您能理解为什么这不能是隐式转换。它实际上涉及在内存中移动内容。
Both
foo
andexecv
requirechar * const *
argument,
是的。
but while my
foo
works (I get success in the output) the system callexecv
fails.
得到您期望的输出并不能证明您的代码是正确的。该调用表现出未定义的行为,因为它的参数与参数类型不匹配,但这似乎没有什么实际效果,因为 foo()
的实现不以任何方式使用该参数。更一般地说,原则上,您的代码完全可以表现出任何行为,因为这就是 "undefined" 的意思。
I would like to know why. Does this have something to do with the implementation of
execv
?
从标准的角度来看,这两个调用都表现出同样未定义的行为。然而,作为一个实际问题,我们知道 execv
确实使用了它的参数,因此与调用 foo
产生您预期的行为相比,该调用产生您预期的行为要令人惊讶得多您期望的行为。
主要问题是2D arrays are arrays of arrays, and arrays are not pointers。因此,您的二维数组 myargs
对于任一函数的参数都没有正确的类型。
Also, assuming I have a char** variable - how can I send it to execv?
您的代码中没有这样的变量,但如果有,您可以将其转换为适当的类型:
char *some_args[] = { "/bin/ls", NULL };
execv((char * const *) some_args);
实际上,如果您也省略强制转换,大多数编译器可能会接受它,尽管标准确实需要它。最好首先声明一个具有正确类型的变量:
char * const correct_args[] = { "/bin/ls", NULL };
execv(correct_args);
另请注意,虽然数组不是指针,但它们在大多数上下文中转换 为指针——我在示例代码中使用的——但仅限于顶层。因此,数组的数组 "decays" 指向指向数组的指针,而不是指向指针的指针。