35

请看下面的代码。它尝试将数组作为 achar**传递给函数:

#include <stdio.h>
#include <stdlib.h>

static void printchar(char **x)
{
    printf("Test: %c\n", (*x)[0]);
}

int main(int argc, char *argv[])
{
    char test[256];
    char *test2 = malloc(256);

    test[0] = 'B';
    test2[0] = 'A';

    printchar(&test2);            // works
    printchar((char **) &test);   // crashes because *x in printchar() has an invalid pointer

    free(test2);

    return 0;
}

我只能通过显式强制转换来编译它,这一事实&test2暗示char**了这段代码是错误的。

不过,我想知道它到底有什么问题。我可以将指针传递给指向动态分配数组的指针,但不能将指针传递给堆栈上数组的指针。当然,我可以通过首先将数组分配给一个临时变量来轻松解决该问题,如下所示:

char test[256];
char *tmp = test;
test[0] = 'B';
printchar(&tmp);

char[256]不过,有人可以向我解释为什么直接投射不起作用char**吗?

4

7 回答 7

38

test是一个数组,而不是一个指针,并且&test是一个指向数组的指针。它不是指向指针的指针。

你可能被告知数组是一个指针,但这是不正确的。数组的名称是整个对象的名称——所有元素。它不是指向第一个元素的指针。在大多数表达式中,数组会自动转换为指向其第一个元素的指针。这是一种通常有用的便利。但这条规则有三个例外:

  • 数组是 的操作数sizeof
  • 数组是 的操作数&
  • 数组是用于初始化数组的字符串文字。

&test中,数组是 的操作数&,所以不会发生自动转换。的结果&test是指向 256 数组的指针,该数组的char类型为char (*)[256],而不是char **

要获得指向charfrom的指针test,您首先需要创建指向 的指针char。例如:

char *p = test; // Automatic conversion of test to &test[0] occurs.
printchar(&p);  // Passes a pointer to a pointer to char.

考虑这个问题的另一种方法是实现test命名整个对象——整个数组 256 char。它没有命名指针,因此,在 中&test,没有可以获取地址的指针,因此无法生成char **。为了创建一个char **,您必须首先拥有一个char *.

于 2020-02-01T13:21:51.780 回答
29

因为test不是指针。

&test得到一个指向数组的指针,类型为char (*)[256],它不兼容char**(因为数组不是指针)。这会导致未定义的行为。

于 2020-02-01T12:59:44.893 回答
6

的类型test2char *。因此, 的类型&test2将与的char **参数类型兼容。 的类型是。因此, 的类型将与的参数类型兼容。xprintchar()
testchar [256]&testchar (*)[256]xprintchar()

让我向您展示 和 的地址方面的test区别test2

#include <stdio.h>
#include <stdlib.h>

static void printchar(char **x)
{
    printf("x = %p\n", (void*)x);
    printf("*x  = %p\n", (void*)(*x));
    printf("Test: %c\n", (*x)[0]);
}

int main(int argc, char *argv[])
{
    char test[256];
    char *test2 = malloc(256);

    test[0] = 'B';
    test2[0] = 'A';

    printf ("test2 : %p\n", (void*)test2);
    printf ("&test2 : %p\n", (void*)&test2);
    printf ("&test2[0] : %p\n", (void*)&test2[0]);
    printchar(&test2);            // works

    printf ("\n");
    printf ("test : %p\n", (void*)test);
    printf ("&test : %p\n", (void*)&test);
    printf ("&test[0] : %p\n", (void*)&test[0]);

    // Commenting below statement
    //printchar((char **) &test);   // crashes because *x in printchar() has an invalid pointer

    free(test2);

    return 0;
}

输出:

$ ./a.out 
test2 : 0x7fe974c02970
&test2 : 0x7ffee82eb9e8
&test2[0] : 0x7fe974c02970
x = 0x7ffee82eb9e8
*x  = 0x7fe974c02970
Test: A

test : 0x7ffee82eba00
&test : 0x7ffee82eba00
&test[0] : 0x7ffee82eba00

这里要注意的一点:

test2和的输出(内存地址)在&test2[0]数字上相同,它们的类型也相同,即char *.
但是test2&test2是不同的地址,它们的类型也不同。
的类型test2char *
的类型&test2char **

x = &test2
*x = test2
(*x)[0] = test2[0] 

和的输出(内存地址)在数值上相同test它们的类型不同。 的类型是。 的类型是。 的类型是。 &test&test[0]
testchar [256]
&testchar (*) [256]
&test[0]char *

如输出所示&test,与 相同&test[0]

x = &test[0]
*x = test[0]       //first element of test array which is 'B'
(*x)[0] = ('B')[0]   // Not a valid statement

因此,您遇到了分段错误。

于 2020-02-01T14:06:14.040 回答
3

您的代码期望参数xofprintchar指向包含(char *).

在第一次调用中,它指向用于的存储,test2因此确实是一个指向 a 的值,(char *)后者指向分配的内存。

然而,在第二次调用中,没有(char *)可能存储任何此类值的地方,因此不可能指向此类内存。您添加的强制转换(char **)将删除编译错误(关于转换(char *)(char **)),但它不会使存储看起来凭空出现以包含(char *)初始化以指向 test.xml 的第一个字符。C 中的指针转换不会改变指针的实际值。

为了得到你想要的,你必须明确地做到这一点:

char *tempptr = &temp;
printchar(&tempptr);

我假设您的示例是对一段更大的代码的提炼;例如,也许您想printchar增加(char *)传递的x值指向的值,以便在下一次调用时打印下一个字符。如果不是这种情况,为什么不直接传递一个(char *)指向要打印的字符,或者甚至只是传递字符本身呢?

于 2020-02-01T22:18:47.023 回答
3

您不能访问指向指针的指针,因为&test它不是指针——它是一个数组。

如果你取一个数组的地址,将数组和数组的地址转换为(void *),然后比较它们,它们将(除非可能的指针迂腐)是等价的。

您真正在做的事情与此类似(同样,除非有严格的混叠):

putchar(**(char **)test);

这显然是错误的。

于 2020-02-01T17:21:46.980 回答
0

显然,获取地址与test获取地址相同test[0]

#include <stdio.h>
#include <stdlib.h>

static void printchar(char **x)
{
    printf("[printchar] Address of pointer to pointer: %p\n", (void *)x);
    printf("[printchar] Address of pointer: %p\n", (void *)*x);
    printf("Test: %c\n", **x);
}

int main(int argc, char *argv[])
{
    char test[256];
    char *test2 = malloc(256);

    printf("[main] Address of test: %p\n", (void *)test);
    printf("[main] Address of the address of test: %p\n", (void *)&test);
    printf("[main] Address of test2: %p\n", (void *)test2);
    printf("[main] Address of the address of test2: %p\n", (void *)&test2);

    test[0] = 'B';
    test2[0] = 'A';

    printchar(&test2);            // works
    printchar(&test);   // crashes because *x in printchar() has an invalid pointer

    free(test2);

    return 0;
}

编译并运行:

forcebru$ clang test.c -Wall && ./a.out
test.c:25:15: warning: incompatible pointer types passing 'char (*)[256]' to
      parameter of type 'char **' [-Wincompatible-pointer-types]
    printchar(&test);   // crashes because *x in printchar() has an inva...
              ^~~~~
test.c:4:30: note: passing argument to parameter 'x' here
static void printchar(char **x)
                             ^
1 warning generated.
[main] Address of test: 0x7ffeeed039c0
[main] Address of the address of test: 0x7ffeeed039c0 [THIS IS A PROBLEM]
[main] Address of test2: 0x7fbe20c02aa0
[main] Address of the address of test2: 0x7ffeeed039a8
[printchar] Address of pointer to pointer: 0x7ffeeed039a8
[printchar] Address of pointer: 0x7fbe20c02aa0
Test: A
[printchar] Address of pointer to pointer: 0x7ffeeed039c0
[printchar] Address of pointer: 0x42 [THIS IS THE ASCII CODE OF 'B' in test[0] = 'B';]
Segmentation fault: 11

所以分段错误的最终原因是该程序将尝试取消引用绝对地址0x42(也称为'B'),您的程序没有读取权限。

尽管使用不同的编译器/机器地址会有所不同:在线尝试!,但由于某种原因,你仍然会得到这个:

[main] Address of test: 0x7ffd4891b080
[main] Address of the address of test: 0x7ffd4891b080  [SAME ADDRESS!]

但是导致分段错误的地址很可能是不同的:

[printchar] Address of pointer to pointer: 0x7ffd4891b080
[printchar] Address of pointer: 0x9c000000942  [WAS 0x42 IN MY CASE]
于 2020-02-01T12:57:30.433 回答
-4

的表示char [256]取决于实现。它不能与 相同char *

&test类型char (*)[256]转换为char **产生未定义的行为。

对于某些编译器,它可能会达到您的预期,而其他编译器则不会。

编辑:

在使用 gcc 9.2.1 进行测试后,似乎printchar((char**)&test)实际上test 作为值转换为char**. 就好像指令是printchar((char**)test)。在printchar函数中,x是指向数组 test 的第一个字符的指针,而不是指向第一个字符的双精度指针。双重取消引用x会导致分段错误,因为数组的前 8 个字节不对应于有效地址。

使用 clang 9.0.0-2 编译程序时,我得到了完全相同的行为和结果。

这可能被视为编译器错误,或未定义行为的结果,其结果可能是编译器特定的。

另一个意想不到的行为是代码

void printchar2(char (*x)[256]) {
    printf("px: %p\n", *x);
    printf("x: %p\n", x);
    printf("c: %c\n", **x);
}

输出是

px: 0x7ffd92627370
x: 0x7ffd92627370
c: A

奇怪的行为是,x并且*x具有相同的价值。

这是编译器的事情。我怀疑这是由语言定义的。

于 2020-02-01T13:04:08.647 回答