我知道这个话题已经讨论过好几次了,我想我基本上知道数组和指针之间的区别,但我对数组如何精确存储在mem中感兴趣。
例如:
const char **name = {{'a',0},{'b',0},{'c',0},0};
printf("Char: %cn", name[0][0]); // This does not work
但如果它像这样声明:
const char *name[] = {"a","b","c"};
printf("Char: %cn", name[0][0]); // Works well
一切都很好。
当你定义一个变量时,比如
char const* str = "abc";
char const** name = &str;
它看起来像这样:
+---+ +---+ +---+---+---+---+
| *-+---->| *-+--->| a | b | c | 0 |
+---+ +---+ +---+---+---+---+
使用表单定义变量时
char const* name[] = { "a", "b", "c" };
您有一个指针数组。这看起来像这样:
+---+ +---+---+
| *-+---->| a | 0 |
+---+ +---+---+
| *-+---->| b | 0 |
+---+ +---+---+
| *-+---->| c | 0 |
+---+ +---+---+
可能令人困惑的是,当你把这个数组传递到某个地方时,它会衰减成一个指针,你得到这个:
+---+ +---+ +---+---+
| *-+---->| *-+---->| a | 0 |
+---+ +---+ +---+---+
| *-+---->| b | 0 |
+---+ +---+---+
| *-+---->| c | 0 |
+---+ +---+---+
也就是说,您将获得指向数组第一个元素的指针。递增此指针将移动到数组的下一个元素。
字符串文本隐式转换为char const*
。
大括号初始值设定项没有。
与您的示例无关,但值得了解:直到并包括 C++03,字符串文字也可以隐式转换为char*
(无const
),以便与旧 C 兼容,但令人高兴的是,在 C++11 中,这种不安全的转换终于被删除了。
第一个代码段不起作用的原因是编译器将字符序列重新解释为指针的值,然后忽略其余的初始值设定项。为了使代码片段正常工作,您需要告诉编译器您正在声明一个数组,并且该数组的元素本身就是数组,如下所示:
const char *name[] = {(char[]){'a',0},(char[]){'b',0},(char[]){'c',0},0};
通过此修改,您的程序可以工作并产生所需的输出(链接到 ideone)。
您的第一个示例声明了一个指向 char 的指针。 第二个声明指向字符的指针数组。 不同之处在于,第一层中还有一层间接寻址。 没有图纸有点难以描述。
以假组装风格,
char **name = {{'a',0},{'b',0},{'c',0},0};
将翻译为以下内容:
t1: .byte 'a', 0
.align somewhere; possibly somewhere convenient
t2: .byte 'b', 0
.align
t3: .byte 'c', 0
.align
t4: .dword t1, t2, t3, 0
name: .dword t4
而第二个,
char *name[] = {"a","b","c"};
可能会为 T1、T2 和 T3 生成相同的代码,但随后会这样做
name: .dword t1, t2, t3
这有意义吗?
数组作为连续的对象序列存储在内存中,其中该对象的类型是数组的基类型。 因此,对于您的阵列:
const char *name[] = {"a","b","c"};
数组的基类型为const char *
,数组的大小为 3(因为初始化器有三个元素)。 它在内存中看起来像这样:
| const char * | const char * | const char * |
请注意,数组的元素是指针 - 实际字符串不存储在数组中。 这些字符串中的每一个都是一个字符串文字,它是一个char
数组。 在这种情况下,它们都是两个char
的数组,因此内存中的其他地方有三个未命名的数组:
| 'a' | 0 |
| 'b' | 0 |
| 'c' | 0 |
初始化器将name
数组的三个元素设置为指向这三个未命名数组的初始元素。name[0]
指向'a'
,name[1]
指向'b'
,name[2]
指向'c'
。
你必须看看当你声明一个变量时会发生什么,以及存储变量数据的内存去哪里。
首先,简单地写是什么意思:
char x = 42;
您获得足够的字节来在堆栈上保存 char,并且这些字节设置为值 42。
其次,当你声明一个数组时会发生什么:
char x[] = "hello";
您在堆栈上获得 6 个字节,它们设置为字符 H、E、L、L、O 和值零。
现在,如果您声明一个字符指针会发生什么:
const char* x = "hello";
"hello"的字节存储在静态内存中的某个位置,您将获得足够的字节来保存堆栈上的指针,并且其值设置为保存字符串值的静态内存的第一个字节的地址。
那么现在当你把它声明为第二个例子时会发生什么?你会得到三个存储在静态内存中的独立字符串,"a"、"b"和"c"。然后在堆栈上,您将获得一个由三个指针组成的数组,每个指针都设置为这三个字符串的内存位置。
那么你的第一个例子想做什么?看起来你想要一个指向指针数组的指针,但问题是这个指针数组会去哪里?这就像我上面的指针示例,其中应该在静态内存中分配某些内容。但是,碰巧您无法使用这样的大括号初始化在静态内存中声明二维数组。因此,您可以通过将数组声明为函数外部的变量来执行所需的操作:
const char* name_pointers[] = {"a", "b", "c"};
然后在函数内部:
const char** name = name_pointers;