#include <stdio.h>
#include <string.h>
int main()
{
char *p = "hello";
char q[] = "hello"; // no need to count this
printf("%zu\n", sizeof(p)); // => size of pointer to char -- 4 on x86, 8 on x86-64
printf("%zu\n", sizeof(q)); // => size of char array in memory -- 6 on both
// size_t strlen(const char *s) and we don't get any warnings here:
printf("%zu\n", strlen(p)); // => 5
printf("%zu\n", strlen(q)); // => 5
return 0;
}
9条答案
按热度按时间eqoofvh91#
char*
和char[]
* 是不同的类型 *,但这并不是在所有情况下都能立即显现出来。这是因为数组 * 会衰减为指针 *,这意味着如果提供了一个char[]
类型的表达式,而该表达式应该是char*
类型的,编译器会自动将数组转换为指向其第一个元素的指针。你的示例函数
printSomething
需要一个指针,所以如果你试图像这样传递一个数组给它:字符串
编译器假装你写了这个:
型
mwg9r5ms2#
让我们看看:
字符串
foo* 和foo[]是不同的类型,编译器对它们的处理方式也不同(指针=地址+指针类型的表示,数组=指针+数组的可选长度,如果已知的话,例如,如果数组是静态分配的话),细节可以在标准中找到。在运行时级别上,它们之间没有区别(在汇编程序中,嗯,几乎是这样,见下文)。
另外,在C FAQ中还有一个相关的question:
问:这些初始化之间有什么区别?
型
如果我试图给p[i]赋值,我的程序就会崩溃。
A:字符串文字(C源代码中双引号字符串的正式术语)可以有两种稍微不同的使用方式:
1.作为char数组的初始化器,就像在char a[]的声明中一样,它指定该数组中字符的初始值(如果需要,还指定其大小)。
1.在其他地方,它会变成一个未命名的静态字符数组,这个未命名的数组可能存储在只读存储器中,因此不需要修改。在表达式上下文中,数组会像往常一样立即转换为指针(参见第6节),因此第二个声明会初始化p,使其指向未命名数组的第一个元素。
有些编译器有一个开关来控制字符串文字是否可写(用于编译旧代码),而有些编译器可能有选项来使字符串文字被正式视为常量字符数组(用于更好地捕捉错误)。
另请参见问题1.31、6.1、6.2、6.8和11.8b。
参考:K&R2第5.5节第104页
ISO第6.1.4节和第6.5.7节
依据第3.1.4节
健康与安全第2.7.4节,第31-2页
7ivaypg93#
在C语言中,字符数组和字符指针有什么区别?
C99 N1256草案
字符串文字有两种不同的用法:
1.初始化
char[]
:字符串
这是“更神奇的”,并在6. 7. 8/14“初始化”中进行了描述:
字符类型的数组可以由字符串文字初始化,也可以用大括号括起来。字符串文字的连续字符(如果有空间或数组大小未知,则包括终止的空字符)初始化数组的元素。
所以这只是一个捷径:
型
与任何其他常规数组一样,
c
也可以修改。所以当你写:
型
这类似于:
型
请注意从
char[]
到char *
的隐式强制转换,这始终是法律的的。然后,如果修改
c[0]
,也会修改__unnamed
,即UB。在6.4.5“字符串文字”中对此进行了说明:
5在转换阶段7中,将一个值为零的字节或代码附加到从一个或多个字符串文字产生的每个多字节字符序列。然后使用该多字节字符序列来初始化一个静态存储持续时间和长度刚好足以包含该序列的数组。对于字符串文字,数组元素的类型为char。并使用多字节字符序列的各个字节进行初始化[...]
6如果这些数组的元素具有适当的值,则未指定这些数组是否不同。如果程序试图修改这样的数组,则行为未定义。
6.7.8/32“初始化”给出了一个直接的例子:
示例8:声明
型
定义了“纯”char数组对象
s
和t
,它们的元素是用字符串文字初始化的。此声明与
型
数组的内容是可以修改的。另一方面,声明
型
定义了类型为“pointer to char”的
p
,并将其初始化为指向类型为“array of char”、长度为4的对象,该对象的元素是用字符串文字初始化的。如果试图使用p
修改数组的内容,则该行为未定义。GCC 4.8 x86-64 ELF实作
程序:
型
编译和反编译:
型
输出包含:
型
结论:GCC将
char*
存储在.rodata
部分,而不是.text
部分。如果我们对
char[]
执行相同的操作:型
我们得到:
型
因此它被存储在堆栈中(相对于
%rbp
)。但是请注意,默认的链接器脚本将
.rodata
和.text
放在同一个段中,该段具有执行权限,但没有写入权限。型
其包含:
型
fhg3lkii4#
你不能改变一个字符串常量的内容,这是第一个
p
指向的。第二个p
是一个用字符串常量初始化的数组,你可以改变它的内容。xzlaal3s5#
对于这样的情况,效果是一样的:最终传递的是字符串中第一个字符的地址。
但声明显然是不一样的。
下面的代码为字符串和字符指针留出内存,然后将指针重新定位为指向字符串中的第一个字符。
字符串
而下面的代码只为字符串留出内存。所以它实际上可以使用更少的内存。
型
6fe3ivhb6#
来自 APUE,第5.14节:
字符串
.对于第一个模板,名称在堆栈上分配,因为我们使用数组变量。然而,对于第二个名称,我们使用指针。在这种情况下,只有指针本身的内存驻留在堆栈上;编译器将字符串存储在可执行文件的只读段中。当
mkstemp
函数试图修改字符串时,会发生分段错误。引用的文字与@Ciro Bullli的解释相符。
7ivaypg97#
在我的记忆中,数组实际上是一组指针。
字符串
是一个真实的说法
zkure5ic8#
char p[3] = "hello"
?应该是char p[6] = "hello"
记住,在C中,“字符串”的末尾有一个“\0”字符。在C语言中,数组只是一个指向内存中调整对象的第一个对象的指针。唯一不同的是在语义上。虽然你可以改变指针的值来指向内存中的不同位置,但数组在创建后,将始终指向相同的位置。
另外,当使用数组时,“新建”和“删除”会自动为您完成。
wfveoks09#
看起来一样,但有细微的差别。
下面是
char
数组的声明:字符串
这意味着
p
是一个右值,这意味着你可以使用p
作为这个数组的别名。但实际上你可以使用p
作为数组的地址(或数组的第一个元素)。但你不能像对指针那样对它进行操作,你不能这样做:型
作为所有初始化(全局)变量,它将被放置在ELF文件的
.data
部分:型
在这种情况下,我们也声明了char指针并初始化它:
型
但是用于初始化的字符串文字将放在.
rodata
部分,而字符指针本身将放在ELF文件的.data
部分:型