c - 当 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.0
和5.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
}
解决方案
char (*)[N]
并且char**
是非常不同的东西。
char (*)[N]
是指向 s 数组的指针N
char
。给定char arr[N]
andchar (*parr)[N] = &arr
,事情在内存中的布局如下:
parr arr
┌──────┐ ┌───┬───┬───┬───┬─────┬─────┐
│ │ │ │ │ │ │ │ │
│ ───┼─────►│ 0 │ 1 │ 2 │ 3 │ ... │ N-1 │
│ │ │ │ │ │ │ │ │
└──────┘ └───┴───┴───┴───┴─────┴─────┘
char**
是指向 a 的指针char*
。给定char arr[N]
,char* pchar = arr
和char** ppchar = &pchar
, 事情在内存中的布局如下:
ppchar pchar arr
┌──────┐ ┌──────┐ ┌───┬───┬───┬───┬─────┬─────┐
│ │ │ │ │ │ │ │ │ │ │
│ ───┼─────►│ ───┼─────►│ 0 │ 1 │ 2 │ 3 │ ... │ N-1 │
│ │ │ │ │ │ │ │ │ │ │
└──────┘ └──────┘ └───┴───┴───┴───┴─────┴─────┘
如您所见, achar**
需要 achar*
指向,但char*
在第一种情况下没有。只有一个char[N]
和一个直接指向它的指针。如果您尝试强制parr
转换为 achar**
并取消引用它,您最终会陷入未定义行为的境地,试图将 a 的前几个字节解释arr
为 a char*
,而事实并非如此。
所以让我们看看你的具体情况。您a
和b
数组的布局如下:
┌──────┬──────┬─────────┬──────┐ ┌──────┬───────┬─────────┬──────┐
│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
是一个包含两个char
s数组的数组。
注意:在许多这些情况下,我假设编译器在面对未定义的行为时会做什么。虽然我在这里的分析是实现最终可能会做的事情,但未定义的行为是未定义的。面对未定义的行为,所有逻辑都会消失,并且对于编译器和/或程序在遇到行为未定义的情况时可以做什么没有任何限制。
printf("1. %s, %s\n", a[0], b[0])
: 这可以。- 您索引
a
并获得一个char*
- 您索引
b
并获得 achar[N]
,当传递给它时printf
将衰减为 achar*
到 的第一个元素b[0]
。
- 您索引
printf("2. %s, %s\n", *a, *b)
: 这可以。- 在这种情况下,
a
衰减为char**
指向 的第一个元素a
。您取消引用它以获得char*
. - 在这种情况下,
b
衰减为char (*)[N]
指向 的第一个元素b
。您取消引用它以获得 achar[N]
,当传递给它时printf
将衰减为char*
指向 的第一个元素的ab[0]
。
- 在这种情况下,
printf("3. %s, %s\n", *(char **)a, *(char **)b)
: 这是无效的。- 投射
a
到char**
很好。这与在大多数情况下隐式发生的转换相同。之后,一切都与情况(2)完全相同。 - 转换
b
为char**
无效。没有char*
可指的。发生的事情是b
衰减到char (*)[N]
你然后强行转换到的 achar**
。取消引用该指针会将 的前几个字节b[0]
视为指向 a 的指针char
,但事实并非如此。在这种情况下您的程序的行为是未定义的。
- 投射
printf("4. %s, %s\n", *(char **)a, *(char (*)[N])b)
:这很好,与情况(2)完全相同,只是显式指定了隐式衰减转换。-
printf("5.0. %s, %s\n", (char (*)[N])a, *(char (*)[N])b)
: 这是无效的。- 转换
a
为char (*)[N]
无效。在这种情况下,a
衰减为 achar**
然后您将其强制转换为 achar (*)[N]
。printf
然后会将char (*)[N]
你传递给它的转换为 achar*
,但它不指向 achar
,它指向 achar*
。printf
最终可能会将 s 的字节解释char*
a[0]
为char
s。但是,在这种情况下,您的程序的行为是未定义的。
- 转换
printf("5.1. %s, %s\n", *(char (*)[N])a, *(char (*)[N])b)
: 这是无效的。- 转换
a
为char (*)[N]
无效。在这种情况下,a
衰减为 achar**
然后您将其强制转换为 achar (*)[N]
。当您取消引用时,您最终会解释 s 的第一个N
字节,char*
a[0]
就好像它们是char
s 一样。在这种情况下,您的程序的行为是未定义的。 - 如前所述,强制
b
转换char (*)[N]
为很好,并且与情况(2)完全相同。
- 转换
printf("5.2. %s, %s\n", **(char (*)[N])a, *(char (*)[N])b)
: 这是无效的。- 转换
a
为char (*)[N]
无效。在这种情况下,a
衰减为 achar**
然后您将其强制转换为 achar (*)[N]
。解引用产生一个char[N]
. 取消引用将导致它衰减为char*
实际指向衰减char**
时创建的临时a
对象。printf
最终可能会尝试将这些字节解释为char
s。在这种情况下,它可能最终会离开当前映射页面的末尾,从而导致段错误,但该结果是无法预测的。在这种情况下,您的程序的行为是未定义的,任何事情都可能发生。 - 如前所述,强制
b
转换char (*)[N]
为很好,并且与情况(2)完全相同。
- 转换
-
printf("6.0. %s, %s\n", (char (**)[N])a, *(char (*)[N])b)
: 这是无效的。- 转换
a
为char (**)[N]
无效。发生的事情是a
衰减为 achar**
,然后您将其强制转换为char (**)[N]
(指向N
char
s 数组的指针的指针)。printf
然后可能会将其char (**)[N]
(实际上指向 achar*
,而不是 achar (*)[N]
)解释为 achar*
并最终将 the 的字节解释char*
a[0]
为char
s。在这种情况下,您的程序的行为是未定义的。 - 如前所述,强制
b
转换char (*)[N]
为很好,并且与情况(2)完全相同。
- 转换
printf("6.1. %s, %s\n", *(char (**)[N])a, *(char (*)[N])b)
: 这是无效的。- 转换
a
为char (**)[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)完全相同。
- 转换
推荐阅读
- java - 无法将命令代理到远程服务器。原始错误:错误:读取 ECONNRESET
- azure-active-directory - 从 Azure AD B2C 自定义策略调用受 OAuth 保护的 REST api
- pygame - 似乎无法同时选择多个圈子
- mobile - 带有附加/前缀下拉列表的文本表单
- optaplanner - 附近选择的条件概率分布 [Optaplanner]
- c# - 在 Visual Studio 的新解决方案中使用来自 github 的 Boostrap 模板
- c# - 是否存在 TaskPool 参考实现?
- elasticsearch - 为什么我的 Elasticsearch 查询检索所有索引文档
- c# - 如何在新记录水晶报表中显示上一条记录中某个公式的值
- loadrunner - 在 LOADRunner 中使用公式作为参数