char **p,char *p[]和char p[][]之间的区别是什么?

15
char *p = "some string"   
创建一个指向包含字符串的块的指针p。
char p[] = "some string" 
创建一个包含字面量的字符数组。
第一个是常量声明。它与二维数组相同吗?
什么是区别?
char **p,char *p[],char p[][]. 

我了解到使用char **p会创建一个指针数组,因此与使用char p[][]相比,这种方式会带来开销,用于存储指针值。

前两个声明创建了常量数组。当我尝试修改main(int argc,char **argv)中的argv内容时,并没有出现运行时错误。这是因为它们在函数原型中声明了吗?


4
这些东西非常令人困惑。首先重要的是要理解,“创建”这个术语必须非常谨慎地使用——“声明”和“创建”不是*同一个意思。 - Hot Licks
1
阅读comp.lang.c FAQ的第6节。 - Keith Thompson
2
这个问题已经被讨论无数次了。在提问前请先进行一些研究。 - user529758
1
特别注意,作为对象声明和参数声明,“char foo []”具有不同的含义。 - Keith Thompson
1
仅为完整起见,还有 char (*p)[],它是指向字符数组的指针。 - aragaer
显示剩余3条评论
3个回答

17

普通声明(非函数参数)

char **p; 声明一个指向指针的指针,其中指针指向 char。它为指针保留了空间,但没有为指向的指针或任何 char 保留空间。

char *p[N]; 声明了一个包含 N 个指向 char 的指针数组,它为 N 个指针保留了空间,但没有为任何 char 保留空间。必须明确提供 N 的值,或者在定义时使用初始化值暗示编译器计算。

char p[M][N]; 声明了一个由 MNchar 数组组成的数组,它为 MNchar 保留了空间。没有涉及到指针。必须明确提供 N 的值和 M 的值,或者在定义时使用初始化值暗示编译器计算。

函数参数中的声明

char **p 声明一个指向指针的指针,其中指针指向 char。在函数调用时,会为该指针提供空间(通常在堆栈或处理器寄存器上)。不会为指向的指针或任何 char 保留空间。

char *p[N] 被调整为 char **p,因此与上面相同。忽略 N 的值,并且可以省略 N。(一些编译器可能会计算 N 的值,如果它是带有副作用的表达式,例如 printf("Hello, world.\n"),则这些效果可能会发生在函数被调用时。C标准对此不太清楚。)

char p[M][N] 被调整为 char (*p)[N],所以它是一个指向包含 N 个 char 的数组的指针。忽略了 M 的值,M 可能不存在。必须提供 N。在函数被调用时,会为指针提供空间(通常在堆栈或处理器寄存器中)。不会为 Nchar 的数组保留空间。

argv

argv 是由调用 main 的特殊软件创建的。它填充了从 "环境" 获取的数据。您可以修改其中的 char 数据。

在您的定义 char *p = "some string"; 中,您不允许修改 p 指向的数据,因为 C 标准规定字符串字面量中的字符不得修改。(严格来说,C 标准没有定义如果尝试修改时的行为。)在此定义中,p 不是一个数组;它是指向数组中第一个 char 的指针,而这些 char 在字符串字面量中,并且您不允许修改字符串字面量的内容。

在您的定义 char p[] = "some string"; 中,您可以修改 p 的内容。它不是一个字符串字面量。在这种情况下,字符串字面量在运行时实际上不存在;它只是用来指定如何初始化数组 p 的东西。一旦初始化了 p,就可以修改它。

argv 设置的数据以允许您修改它(因为 C 标准指定了这一点)。


我认为这是我读过的关于这个混乱问题最清晰的讨论。 - Hot Licks
在编程中,当我们使用一些值对数组进行初始化时,在第二种情况(char *p[N])中不需要提供额外的n,而在第三种情况(char p[M][N])中也不需要提供m。 - programer8

6

以下是从内存寻址的角度描述一些更多的差异:

I. char **p; p 是类型为 char 的双指针

声明:

char a = 'g';
char *b = &a;
char **p = &b;


   p                    b                    a     
+------+             +------+             +------+
|      |             |      |             |      |              
|0x2000|------------>|0x1000|------------>|   g  | 
|      |             |      |             |      |
+------+             +------+             +------+
 0x3000               0x2000               0x1000
Figure 1: Typical memory layout assumption   

在上述声明中,a 是包含字符 gchar 类型。指针 b 包含现有字符变量 a 的地址。现在 b 是地址 0x1000*b 是字符 g。最后将 b 的地址分配给 p,因此 a 是字符变量,b 是指针,而 p 是指向指针的指针。这意味着 a 包含值,b 包含地址,如下图所示,p 包含地址的地址。

在各自的系统上,sizeof(p) = sizeof(char *)

II. char *p[M]; p 是字符串数组

声明:

char *p[] = {"Monday", "Tuesday", "Wednesday"};


      p
   +------+  
   | p[0] |       +----------+
0  | 0x100|------>| Monday\0 |              
   |      |       +----------+
   |------|       0x100
   | p[1] |       +-----------+
1  | 0x200|------>| Tuesday\0 |
   |      |       +-----------+
   |------|       0x200
   | p[2] |       +-------------+
2  | 0x300|------>| Wednesday\0 |
   |      |       +-------------+ 
   +------+       0x300
Figure 2: Typical memory layout assumption

在这个声明中,p 是一个包含3个指向 char 类型的指针的数组。意味着数组 p 可以包含3个字符串。每个字符串 (Monday, Tuesday & Wednesday) 存储在内存中的某个位置 (0x100, 0x200 & 0x300),它们的地址分别存储在数组 p 中的 (p[0], p[1] & p[2]) 中。因此,它是一个指针数组。

注: char *p[3];

1. p[0], p[1] & p[2] are addresses of strings of type `char *`.
2. p, p+1 & p+2 are address of address with type being `char **`.
3. Accessing elements is through, p[i][j] is char; p[i] is char *; & p is char **

这里 sizeof(p) = 字符���组数量 * 指针所占字节大小

III. char p[M][N]; p 是一个固定长度字符串的数组,维度为 M x N

声明:

char p[][10] = {Monday, Tuesday, Wednesday};


  p  0x1 2 3 4 5 6 7  8  9  10
    +-------------------------+
 0  | M  o n d a y \0 \0 \0 \0|     
 1  | T  u e s d a  y \0 \0 \0| 
 2  | W  e d n e s  d  a  y \0|
    +-------------------------+
 Figure 3: Typical memory layout assumption

在这个例子中,数组p包含3个字符串,每个字符串都包含10个字符。从内存布局可以看出,p是一个大小为MxN的字符二维数组,在我们的例子中是3x10。这对于表示相等长度的字符串非常有用,因为当字符串比声明char *p[]少于10个字符时,可能会浪费内存,而char *p[]没有浪费内存,因为字符串长度未指定,它对于表示不等长度的字符串非常有用。
访问元素与上述情况类似,p[M]是第M个字符串,p[M][N]是第M个字符串的第N个字符。在这里sizeof(p) = (M行 * N列) * sizeof(char)是二维数组的大小。

谢谢!那是一个非常详细的解释。我只想再确认一件事情。我们能够改变字符指针数组中的单个字符吗?这是代码http://codepad.org/2vyx2IbY。在emacs上它给了我一个段错误。 - programer8
@programer8,答案是否定的。在你的代码中,“p[i]”和“f”指向的是常量字符串字面值,不能通过分别赋值“p[i][j] = 'j'”和“f[i] ='j'”来进行修改。请参考这个代码,它说明了一个二维数组和一个字符数组确实允许你修改其中的字符。 - Sunil Bojanapally

0
  • char* a 中的 a 是指向字符数组的指针,可以修改 a
  • char b[] 中的 b 是字符数组。无法修改 b

它们有点兼容 - 在赋值和表达式中,b 可以自动地“退化”为 a,但反过来不行。

当您使用 char** pchar* p[]char p[][] 时,情况非常相似,只是多了一些间接级别。


“char p[][]”是非法的,原因很明显。它必须像“char p[][6]”这样。 - Dietrich Epp
@DietrichEpp 我也注意到了这一点。在向函数传递值时,我们必须提及第二个维度。但是我所说的是命令行参数。 - programer8
@programer8:命令行参数?这与此有什么关系?char p[][]始终是非法的。 - Dietrich Epp

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