为什么 `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;
}

fooexecv 都需要 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" 指向指向数组的指针,而不是指向指针的指针。