C双字符指针的声明和初始化

27

我一直认为声明

char *c = "line";

和...相同

char c[] = "line";

于是我就这样做了。

char **choices = { "New Game", "Continue Game", "Exit" };

这使我得到了一个不兼容的指针类型,原因是

char *choices[] = { "New Game", "Continue Game", "Exit" };

不懂。有什么帮助可以理解吗?


6
C 数组不等同于 C 指针。 - nmichaels
3
在Example1中,c是一个指向char的指针。在Example2中,c是一个char数组。在Example3中,choices是一个指向指向char的指针(并且错误地被分配为char数组)。在Example4中,choices是一个指向char指针的数组(并且被正确地分配为char数组)。 - oosterwal
@oosterwal:对于ex3和ex4,你说“......分配了一个字符数组”,但正确的应该是“......分配了一个指向字符的指针数组”。对于ex3,有一种可能性可以使用复合字面量分配指向字符的指针数组。 - Martin Babacaev
@Martin Babacaev(和其他有兴趣的人):几年前,我遇到了一篇关于顺时针螺旋规则的文章,用于解释一些奇怪声明的含义。我强烈推荐给任何在这方面感到困惑的人:http://c-faq.com/decl/spiral.anderson.html - oosterwal
1
@oosterwal:谢谢。我喜欢这个网站http://www.unixwiz.net/techtips/reading-cdecl.html。它还解释了抽象声明,例如`int (*(*)())()`。 - Martin Babacaev
@Martin Babacaev:那里有很多好东西。感谢分享链接。 - oosterwal
4个回答

18
char *c = "line";

不是同一个意思

char c[] = "line";

这实际上与之相同

static const char hidden_C0[] = "line";
char *c = (char *)hidden_C0;

除了变量 hidden_C0 无法直接访问以外,其他都一样。但如果您转储生成的汇编语言,您会看到它(通常会有一个不是有效C标识符的名称,例如.LC0)。在您的字符串常量数组示例中也是如此:

char *choices[] = { "New Game", "Continue Game", "Exit" };

变成

const char hidden_C0[] = "New Game";
const char hidden_C1[] = "Continue Game";
const char hidden_C2[] = "Exit";

char *choices[] = { (char *)hidden_C0, (char *)hidden_C1, (char *)hidden_C2 };

现在,这是仅适用于字符串常量的特殊情况行为。您不能编写

int *numbers = { 1, 2, 3 };

你必须写

int numbers[] = { 1, 2, 3 };

这就是为什么你无法编写

char **choices = { "a", "b", "c" };

你的困惑是C语言中一个常见的误解的特例,即认为数组和指针是“相同的”。实际上不是这样的。数组就是数组。具有数组类型的变量在被使用时(几乎在所有的情况下)会发生类型衰退而成为指针类型,但在定义时不会。


14

嗯,它们并不相同。对于大多数人来说,把它们看作相同的东西更容易,因此每个人都开始这样想,直到遇到上述问题 :-)

我本来要写一些冗长的东西,但后来我想...其他人肯定已经做过了。他们也确实做到了。这是一个相当好的解释:

http://www.lysator.liu.se/c/c-faq/c-2.html

最简单的思考方式是,当你做类似以下代码时:

  char *foo = "something";

你实际上在做以下事情:

  char randomblob[] = "something";
  char *foo = randomblob;

现在...这并不是一个准确的图片(虽然我不是编译器专家)。但至少让你以稍微更正确的方式思考事情。

所以,回到你的问题,如果理解正确的话(这绝不是保证),你不能在C中执行你的第三行示例。你是对的,有人可以编写一个编译器,在这里执行正确的操作,但gcc没有。然而,第四个示例则会执行“正确的操作”,并为您提供“一个指向每个指向const char数组本身的指针数组”。

我曾经看过一个网页,可以将复杂的C类型翻译成英语。虽然那可能是在90年代初期,但我敢打赌如果你搜索足够的话,它会为你提供比我刚才草率写的更准确的措辞描述。


非常感谢您提供的链接,我一直以为数组和指针是一样的。我想我从来没有真正地阅读过有关数组的内容。对我来说,它总是指向内存中的第一个小框,并从那里开始直到获得 '\0',因此具有其中一个声明或另一个声明似乎是相同的。其他答案也很好,但是您提供的链接为我解决了疑惑。 - vascop
对于这种错误,C语言就像一把锋利的刀。 - Mandrake

5

没问题,只需写下来

char **choices = (char *[]){ "新游戏", "继续游戏", "退出" };

然而,choices 只能用于线性寻址。例如:

printf ("%s", &(*choices)[0]); 输出:新游戏
printf ("%s", &(*choices)[1]); 输出:游戏
printf ("%s", &(*choices)[9]); 输出:继续游戏

所以这不是一个玩笑,它是一个有效的初始化。只是另一种用法。


你也可以在这里找到一个非常相似的例子,解释了 复合字面量 的概念。


我完全支持复合字面量,但是要么这个答案是一个玩笑,要么我完全不明白为什么你会这样做。 - SiegeX
这个表达式在GCC4.2下编译没有任何错误或警告。这个问题是关于编译问题,而不是使用问题。 - Martin Babacaev
2
@SiegeX:我已经更新了我的答案,并提供了如何使用它的示例。 - Martin Babacaev
我应该在我的评论中更加具体。为什么不直接声明'choices'为char *choices[] = { "New Game", "Continue Game", "Exit" };呢? - SiegeX

2

在线C标准(草案n1256):

6.7.8 初始化
...
11 标量的初始化器必须是一个单一表达式,可以用括号括起来。对象的初始值为表达式的值(在转换后);对于简单赋值适用相同的类型约束和转换,将标量的类型视为其声明类型的未限定版本。
...
16 否则,具有聚合或联合类型的对象的初始化器应为元素或命名成员的大括号括起来的初始化器列表。

强调添加。

char **是标量类型,不是聚合类型,因此与初始化器{"New Game", "Continue Game", "Exit"}不兼容。相比之下,char *[]是聚合(数组)类型。

类似地,您不能编写以下内容:

int *foo = {1, 2, 3};

因为int *不是数组类型。

你对

char *c = "line";

并且

char c[] = "line";

有点不同;它们并不是相同的。第一种形式将字符串文字的地址复制到指针值c中。第二种形式将数组表达式"line"的内容复制到由c指定的缓冲区中。


只有在阅读完整个 C 标准后,人们才能认为自己是真正的 C 开发者。 - Mandrake

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接