2

我知道这个话题已经讨论过好几次了,我想我基本上知道数组和指针之间的区别,但我对数组如何准确地存储在 mem 中很感兴趣。

例如:

const char **name = {{'a',0},{'b',0},{'c',0},0};
printf("Char: %c\n", name[0][0]); // This does not work

但如果它像这样声明:

const char *name[] = {"a","b","c"};
printf("Char: %c\n", name[0][0]); // Works well

一切正常。

4

6 回答 6

5

当您定义一个变量时

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 |
          +---+     +---+---+

也就是说,你得到一个指向数组第一个元素的指针。递增这个指针移动到数组的下一个元素。

于 2012-11-15T01:09:29.700 回答
2

字符串文字隐式转换为char const*.

花括号初始化程序没有。

与您的示例无关,但值得知道:直到并包括 C++03,为了与旧 C 兼容,字符串文字也可以隐式转换为char*(no const),但在 C++11 中,这种不安全的转换终于被删除了。

于 2012-11-15T00:42:16.180 回答
1

您的第一个示例声明了一个指向 char 指针的指针。第二个声明了一个指向 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

那有意义吗?

于 2012-11-15T00:53:41.770 回答
1

数组作为连续的对象序列存储在内存中,其中该对象的类型是数组的基本类型。因此,对于您的数组:

const char *name[] = {"a","b","c"};

数组的基本类型是,数组const char *的大小是 3(因为你的初始化程序有三个元素)。它在内存中看起来像这样:

| const char * | const char * | const char * |

请注意,数组的元素是指针——实际的字符串并不存储在数组中。这些字符串中的每一个都是一个字符串文字,它是一个char. 在这种情况下,它们都是两个chars 的数组,所以在内存的其他地方你有三个未命名的数组:

| 'a' |  0  |
| 'b' |  0  |
| 'c' |  0  |

初始化器将name数组的三个元素设置为指向这三个未命名数组的初始元素。 name[0]指向'a',name[1]指向'b'name[2]指向'c'.

于 2012-11-15T01:08:00.020 回答
1

您必须查看声明变量时会发生什么,以及存储变量数据的内存在哪里。

首先,简单地写是什么意思:

char x = 42;

您获得了足够的字节来在堆栈上保存一个字符,并且这些字节被设置为值 42。

其次,当你声明一个数组时会发生什么:

char x[] = "hello";

您在堆栈上获得 6 个字节,它们被设置为字符 h、e、l、l、o 和值零。

现在如果你声明一个字符指针会发生什么:

const char* x = "hello";

“你好”的字节存储在静态内存中的某个位置,您可以获得足够的字节来保存堆栈上的指针,并且它的值设置为保存字符串值的静态内存的第一个字节的地址。

那么现在当你在第二个例子中声明它时会发生什么?您将获得三个单独的字符串存储在静态内存中,“a”、“b”和“c”。然后在堆栈上,您会得到一个由三个指针组成的数组,每个指针都设置为这三个字符串的内存位置。

那么你的第一个例子是什么?看起来你想要一个指向指针数组的指针,但问题是这个指针数组会去哪里?这就像我上面的指针示例,应该在静态内存中分配一些东西。然而,碰巧你不能像这样使用大括号初始化在静态内存中声明一个二维数组。因此,您可以通过将数组声明为函数外部的变量来做您想做的事情:

const char* name_pointers[] = {"a", "b", "c"};

然后在函数内部:

const char** name = name_pointers;
于 2012-11-15T01:09:33.203 回答
1

The reason the first snippet does not work is that the compiler re-interprets the sequence of characters as the value of a pointer, and then ignores the rest of the initializers. In order for the snippet to work, you need to tell the compiler that you are declaring an array, and that the elements of that array are arrays themselves, like this:

const char *name[] = {(char[]){'a',0},(char[]){'b',0},(char[]){'c',0},0};

With this modification in place, your program works and produces the desired output (link to ideone).

于 2012-11-15T00:46:09.297 回答