为什么`execv`不能使用从char **到char * const *的隐式转换?

时间:2018-12-12 14:04:44

标签: c execv

考虑以下代码:

#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)时,系统调用{{1 }}失败。

我想知道为什么。这与execv的实现有关吗?

还要假设我有一个execv变量-如何将其发送到char**

2 个答案:

答案 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)

  

fooexecv都需要char * const *参数,

是的

  

但是当我的foo工作正常(输出成功)时,系统调用execv失败了。

获得期望的输出并不能证明您的代码正确。该调用表现出未定义的行为,因为其参数与参数类型不匹配,但似乎没有什么实际效果,因为foo()的实现未以任何方式使用该参数。更一般而言,原则上,您的代码完全可以表现出任何行为,因为这就是“未定义”的含义。

  

我想知道为什么。这与execv的实现有关吗?

从标准的角度来看,两个调用都表现出同样未定义的行为。但是,实际上,我们知道execv确实使用了它的参数,因此,对于该调用产生您期望的行为,比对foo产生调用的行为更令人惊讶您预期的行为。

主要问题是2D arrays are arrays of arraysarrays 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);

还请注意,尽管数组不是指针,但它们在大多数上下文中都会被转换为指针(我在示例代码中使用过),但它们只是顶层。因此,数组数组会“衰减”到指向数组的指针,而不是指向指针的指针。