首页 > 解决方案 > 当 char (*)[] 转换为 char ** 时,无法按预期执行

问题描述

原来的

为什么不能char (*)[N]转换成char **

char *[N]转换为时发生了什么char (**)[N]?当我转换char *[N]为 时char (**)[N],它确实有效。

#include <stdio.h>

int main() {
    char *a[2]={"a", "a"};
    char b[2][2]={"b", "b"};
    // Expect "a, b"
    printf("%s, %s\n", a[0], b[0]); // 1. OK
    printf("%s, %s\n", *a, *b); // 2. OK
    printf("%s, %s\n", *(char **)a, *(char **)b); // 3. Segmentation fault
    printf("%s, %s\n", *(char **)a, *(char (*)[2])b); // 4. OK
    printf("%s, %s\n", *(char (*)[2])a, *(char (*)[2])b); // 5. Wrong output
    printf("%s, %s\n", *(char (**)[2])a, *(char (*)[2])b); // 6. Correct output
}

已编辑

为什么不能char (*)[N]转换成char **

char *[N]转换为时发生了什么char (**)[N]?当我转换char *[N]为 时char (**)[N],它确实有效。

为什么5.05.1具有相同的值和相同的地址?

的价值是5.2多少?

#include <stdio.h>

#define N 10
// #define S "123456789"
   #define S "abcdefghi"

int main() {
    char *a[2]={S, S};
    char b[2][N]={S, S};
    // Expect "a, b"
    printf("1. %s, %s\n", a[0], b[0]); // 1. OK
    printf("2. %s, %s\n", *a, *b); // 2. OK
    printf("3. %s, %s\n", *(char **)a, *(char **)b); // 3. Segmentation fault
    printf("4. %s, %s\n", *(char **)a, *(char (*)[N])b); // 4. OK
    printf("5.0. %s, %s\n", (char (*)[N])a, *(char (*)[N])b); // 5.0. Wrong output
    printf("5.1. %s, %s\n", *(char (*)[N])a, *(char (*)[N])b); // 5.1. Wrong output
    printf("5.2. %s, %s\n", **(char (*)[N])a, *(char (*)[N])b); // 5.2. Segmentation fault
    printf("6.0. %s, %s\n", (char (**)[N])a, *(char (*)[N])b); // 6.0. Wrong output
    printf("6.1. %s, %s\n", *(char (**)[N])a, *(char (*)[N])b); // 6.1. Correct output
}

标签: cpointers

解决方案


char (*)[N]并且char**是非常不同的东西。

  • char (*)[N]是指向 s 数组的指针N char。给定char arr[N]and char (*parr)[N] = &arr,事情在内存中的布局如下:
parr          arr
┌──────┐      ┌───┬───┬───┬───┬─────┬─────┐
│      │      │   │   │   │   │     │     │
│   ───┼─────►│ 0 │ 1 │ 2 │ 3 │ ... │ N-1 │
│      │      │   │   │   │   │     │     │
└──────┘      └───┴───┴───┴───┴─────┴─────┘
  • char**是指向 a 的指针char*。给定char arr[N],char* pchar = arrchar** ppchar = &pchar, 事情在内存中的布局如下:
ppchar        pchar         arr
┌──────┐      ┌──────┐      ┌───┬───┬───┬───┬─────┬─────┐
│      │      │      │      │   │   │   │   │     │     │
│   ───┼─────►│   ───┼─────►│ 0 │ 1 │ 2 │ 3 │ ... │ N-1 │
│      │      │      │      │   │   │   │   │     │     │
└──────┘      └──────┘      └───┴───┴───┴───┴─────┴─────┘

如您所见, achar**需要 achar*指向,但char*在第一种情况下没有。只有一个char[N]和一个直接指向它的指针。如果您尝试强制parr转换为 achar**并取消引用它,您最终会陷入未定义行为的境地,试图将 a 的前几个字节解释arr为 a char*,而事实并非如此。


所以让我们看看你的具体情况。您ab数组的布局如下:

  ┌──────┬──────┬─────────┬──────┐    ┌──────┬───────┬─────────┬──────┐
  │0     │1     │         │N-1   │    │0     │1      │         │N-1   │
  │ 'a'  │ 'b'  │   ...   │ '\0' │    │ 'a'  │ 'b'   │   ...   │ '\0' │
  │      │      │         │      │    │      │       │         │      │
  └──────┴──────┴─────────┴──────┘    └──────┴───────┴─────────┴──────┘
    ▲                                   ▲
a   │                ┌──────────────────┘
┌───┼────────────┬───┼────────────┐
│0  │            │1  │            │
│   │            │   │            │
│                │                │
│                │                │
└────────────────┴────────────────┘

b
┌──────────────────────────────────┬──────────────────────────────────┐
│ 0                                │ 1                                │
│ ┌──────┬──────┬─────────┬──────┐ │ ┌──────┬──────┬─────────┬──────┐ │
│ │0     │1     │         │N-1   │ │ │0     │1     │         │N-1   │ │
│ │ 'a'  │ 'b'  │   ...   │ '\0' │ │ │ 'a'  │ 'b'  │   ...   │ '\0' │ │
│ │      │      │         │      │ │ │      │      │         │      │ │
│ └──────┴──────┴─────────┴──────┘ │ └──────┴──────┴─────────┴──────┘ │
│                                  │                                  │
└──────────────────────────────────┴──────────────────────────────────┘

也就是说,a是一个包含两个指针的数组,每个指针指向一个s数组的第一个元素,char并且b是一个包含两个chars数组的数组。

注意:在许多这些情况下,我假设编译器在面对未定义的行为时会做什么。虽然我在这里的分析是实现最终可能会做的事情,但未定义的行为是未定义的。面对未定义的行为,所有逻辑都会消失,并且对于编译器和/或程序在遇到行为未定义的情况时可以做什么没有任何限制。

  1. printf("1. %s, %s\n", a[0], b[0]): 这可以。
    • 您索引a并获得一个char*
    • 您索引b并获得 a char[N],当传递给它时printf将衰减为 achar*到 的第一个元素b[0]
  2. printf("2. %s, %s\n", *a, *b): 这可以。
    • 在这种情况下,a衰减为char**指向 的第一个元素a。您取消引用它以获得char*.
    • 在这种情况下,b衰减为char (*)[N]指向 的第一个元素b。您取消引用它以获得 a char[N],当传递给它时printf将衰减为char*指向 的第一个元素的a b[0]
  3. printf("3. %s, %s\n", *(char **)a, *(char **)b): 这是无效的。
    • 投射achar**很好。这与在大多数情况下隐式发生的转换相同。之后,一切都与情况(2)完全相同。
    • 转换bchar**无效。没有char*可指的。发生的事情是b衰减到char (*)[N]你然后强行转换到的 a char**。取消引用该指针会将 的前几个字节b[0]视为指向 a 的指针char,但事实并非如此。在这种情况下您的程序的行为是未定义的。
  4. printf("4. %s, %s\n", *(char **)a, *(char (*)[N])b):这很好,与情况(2)完全相同,只是显式指定了隐式衰减转换。
    1. printf("5.0. %s, %s\n", (char (*)[N])a, *(char (*)[N])b): 这是无效的。
      • 转换achar (*)[N]无效。在这种情况下,a衰减为 achar**然后您将其强制转换为 a char (*)[N]printf然后会将char (*)[N]你传递给它的转换为 a char*,但它不指向 a char,它指向 a char*printf最终可能会将 s 的字节解释char* a[0]chars。但是,在这种情况下,您的程序的行为是未定义的。
    2. printf("5.1. %s, %s\n", *(char (*)[N])a, *(char (*)[N])b): 这是无效的。
      • 转换achar (*)[N]无效。在这种情况下,a衰减为 achar**然后您将其强制转换为 a char (*)[N]。当您取消引用时,您最终会解释 s 的第一个N字节,char* a[0]就好像它们是chars 一样。在这种情况下,您的程序的行为是未定义的。
      • 如前所述,强制b转换char (*)[N]为很好,并且与情况(2)完全相同。
    3. printf("5.2. %s, %s\n", **(char (*)[N])a, *(char (*)[N])b): 这是无效的。
      • 转换achar (*)[N]无效。在这种情况下,a衰减为 achar**然后您将其强制转换为 a char (*)[N]。解引用产生一个char[N]. 取消引用将导致它衰减为char*实际指向衰减char**时创建的临时a对象。 printf最终可能会尝试将这些字节解释为chars。在这种情况下,它可能最终会离开当前映射页面的末尾,从而导致段错误,但该结果是无法预测的。在这种情况下,您的程序的行为是未定义的,任何事情都可能发生。
      • 如前所述,强制b转换char (*)[N]为很好,并且与情况(2)完全相同。
    1. printf("6.0. %s, %s\n", (char (**)[N])a, *(char (*)[N])b): 这是无效的。
      • 转换achar (**)[N]无效。发生的事情是a衰减为 a char**,然后您将其强制转换为char (**)[N](指向N chars 数组的指针的指针)。 printf然后可能会将其char (**)[N](实际上指向 a char*,而不是 a char (*)[N])解释为 achar*并最终将 the 的字节解释char* a[0]chars。在这种情况下,您的程序的行为是未定义的。
      • 如前所述,强制b转换char (*)[N]为很好,并且与情况(2)完全相同。
    2. printf("6.1. %s, %s\n", *(char (**)[N])a, *(char (*)[N])b): 这是无效的。
      • 转换achar (**)[N]无效。正在发生的事情是a衰减为char**,然后您将其强制转换为char (**)[N]。然后,您取消引用它并将 的位解释char* a[0]为好像它们是char (*)[N]. 您在这里得到了您期望的结果,因为您具有相同数量的间接级别,并且恰好a[0]指向 a 的第一个元素char[N]char (*)[N]指向数组和指向该数组的第一个char*元素通常具有完全相同的位模式,因此当printf稍后将char (*)[N]您传递给它的 a解释为char*它会找到您期望的内容时。但这并不能保证;在这种情况下,您的程序的行为是未定义的。
      • 如前所述,强制b转换char (*)[N]为很好,并且与情况(2)完全相同。

推荐阅读