char a[] = ?string?; 有啥区别?和 char *p = ?string?;?

Posted

技术标签:

【中文标题】char a[] = ?string?; 有啥区别?和 char *p = ?string?;?【英文标题】:What is the difference between char a[] = ?string?; and char *p = ?string?;?char a[] = ?string?; 有什么区别?和 char *p = ?string?;? 【发布时间】:2012-03-16 15:28:54 【问题描述】:

正如标题所说,有什么区别

char a[] = ?string?; and 
char *p = ?string?;  

这个问题是在面试时问我的。 我什至不明白这个说法。

char a[] = ?string?

? 运算符是什么?它是字符串的一部分还是有特定的含义?

【问题讨论】:

打赌面试官的意思是“而不是?。?符号用于三级运算符,但这不是它的有效语法。 这可能是mojibake的情况。这不是 C++。 问题可能是使用了开始/结束引号,而您的字体由于某种原因找不到它们,因此将它们呈现为?s。 我的猜测:代码被复制到 MS Word 中,引号被转换,然后以某种方式转换回来。或者缺少#define ? "。不过,不知道这是否可以编译。 difference-between-char-str-string-and-char-str-string 【参考方案1】:
char a[] = "string";

这会在堆栈上分配字符串。

char *p = "string";

这会在堆栈上创建一个指针,该指针指向进程数据段中的文字。

? 是写它的人不知道他们在做什么。

【讨论】:

鉴于问题要求提供一些琐碎的细节,我认为答案应该更深入地探索可能性。具体来说,“char a[]...在堆栈上分配...”假定它在函数内部而不是全局的,并进一步引用a[],而没有提到在函数内部实际上有整个函数的运行时副本文本从常量数据段到堆栈。 char* 用法创建一个非const 指针 - 在堆栈上或作为数据段中的全局 - 并在运行时或(可能)编译时分别初始化它以寻址 const 文本。 这个答案是错误的。第二个代码 sn-p 创建一个编译错误:P @VJovic:确实如此。在 C++03 中不推荐使用没有 const 限定符的字符串字面量指针,因此第二个 sn-p 不是合法的 C++ 代码。 它用一些编译器编译(例如 Microsoft Visual C++ 2010),所以当说它会产生编译错误时,你应该更具体 - 编写编译器版本,或者(如其他答案中提到的)这违反了 c++ 标准(C++03 C++11)。【参考方案2】:

a 声明了一个 char 值数组 -- 一个终止的 chars 数组。

p 声明了一个指针,该指针指向一个不可变的、终止的 C 字符串,其确切存储位置由实现定义。请注意,这应该是const-qualified(例如const char *p = "string";)。

如果您使用std::cout << "a: " << sizeof(a) << "\np: " << sizeof(p) << std::endl; 打印出来,您会看到它们的大小不同(注意:值可能因系统而异):

a: 7
p: 8

这是什么?操作员?它是字符串的一部分还是有特定的含义?

char a[] = ?string?

我假设它们曾经是双引号 "string",可能被转换为“智能引号”,然后无法在此过程中表示,并被转换为 ?

【讨论】:

@Sachan 很少见(例如,当您必须改变 char 缓冲区时)。 如果您不需要改变 char 缓冲区,那么它应该是 [static] const char a[] = "xyz";,而不是 const char* p = "xyz";const char* const p = "xyz"; - 前者意味着 p 可能会被移动到其他地方如果这不是故意的,那么最好不要允许这种可能性,并且都要求编译器为指针和文本提供空间 - 恕我直言 - 只是表明编译器缺乏准确的心理模型,并且浪费空间和未优化构建的时间。 @Justin:我没有侮辱的意图或兴趣。你说我在很多情况下都错了——我只讲了“不需要改变上下文”,所以请解释一下我错了的许多场景,以及[static] const char[]如何导致“浪费空间和时间” "。【参考方案3】:

char *p = "string"; 创建一个指向只读内存的指针,其中存储了字符串文字"string"。尝试修改 p 指向的字符串会导致未定义的行为。

char a[] = "string"; 创建一个数组并使用字符串文字 "string" 初始化其内容。

【讨论】:

【参考方案4】:

? 似乎是一个错字,它在语义上无效。因此,答案假定? 是一个错字,并解释了面试官可能真正想问的问题。


首先,两者明显不同:

    第一个创建一个指针。 第二个创建一个数组。

继续阅读以获得更详细的解释:

数组版本:

char a[] = "string";  

创建一个足够大的数组来保存字符串文字“string”,包括它的NULL 终止符。数组string 用字符串文字“string”初始化。 数组可以在以后修改。此外,即使在编译时,数组的大小也是已知的,因此可以使用 sizeof 运算符来确定其大小。


指针版本:

char *p  = "string"; 

创建一个指向字符串文字“string”的指针。这比数组版本快,但指针指向的字符串不应更改,因为它位于只读实现定义的内存中。修改这样的字符串文字会导致 Undefined Behavior

事实上,C++03 不赞成[Ref 1] 使用不带const 关键字的字符串文字。所以声明应该是:

const char *p = "string";

此外,您需要使用strlen() 函数,而不是sizeof 来查找字符串的大小,因为sizeof 运算符只会为您提供指针变量的大小。


哪个版本更好,我应该使用哪个版本?

取决于用途。

如果您不需要对字符串进行任何更改,请使用指针版本。 如果您打算更改数据,请使用数组版本。

注意:这不是 C++,但这是特定于 C 的。

请注意,在 C 中使用不带 const 关键字的字符串是完全有效的。 但是,修改字符串文字仍然是 C[Ref 2] 中的未定义行为。

这就引出了一个有趣的问题,What is the difference between char* and const char* when used with string literals in C?


对于 Standerdese 粉丝:[参考 1]C++03 标准:§4.2/2

不是宽字符串文字的字符串文字(2.13.4)可以转换为“指向char的指针”类型的右值;宽字符串文字可以转换为“指向 wchar_t 的指针”类型的右值。无论哪种情况,结果都是指向数组第一个元素的指针。仅当存在显式适当的指针目标类型时才考虑这种转换,而不是当一般需要从左值转换为右值时。 [注意:此转换已弃用。见附录 D。] 为了在重载决议 (13.3.3.1.1) 中排名,这种转换被认为是数组到指针的转换,然后是限定转换 (4.4)。 [示例:“abc”转换为“pointer to const char”作为数组到指针的转换,然后转换为“pointer to char”作为限定转换。 ]

C++11 只是删除了上面的引用,这意味着它是 C++11 中的非法代码。

[参考 2]C99 标准 6.4.5/5“字符串文字 - 语义”:

在翻译阶段 7 中,将一个字节或零值代码附加到由一个或多个字符串文字产生的每个多字节字符序列。然后使用多字节字符序列来初始化一个静态存储持续时间和长度刚好足以包含该序列的数组。对于字符串字面量,数组元素的类型为 char,并使用多字节字符序列的各个字节进行初始化;对于宽字符串文字,数组元素的类型为 wchar_t,并使用宽字符序列进行初始化...

如果这些数组的元素具有适当的值,则未指定这些数组是否不同。 如果程序尝试修改这样的数组,则行为未定义。

【讨论】:

非常感谢任何反对投票的技术推理。 “这样更快”是什么意思? 我不同意If you do not need to make any changes to the string, use the pointer version. - 如果您不需要对字符串进行任何更改,您可能想要使用const char a[] = "string";,即只需添加const。当动态链接器在启动期间完成工作时(至少在 Linux 上),它避免了重定位。更多讨论请参见How to write shared libraries 第 2.4.1 节。【参考方案5】:

第一个是数组,另一个是指针。

数组声明char a[6]; 要求为六个字符留出空间,名称为a。也就是说,有一个名为a 的位置可以坐六个字符。另一方面,指针声明char *p; 请求一个存放指针的位置。该指针的名称为p,可以指向任何地方的任何字符(或连续的字符数组)。

声明

 char a[] = "string";
 char *p = "string"; 

会产生可以这样表示的数据结构:

     +---+---+---+---+---+---+----+
  a: | s | t | r | i | n | g | \0 |
     +---+---+---+---+---+---+----+
     +-----+     +---+---+---+---+---+---+---+ 
  p: |  *======> | s | t | r | i | n | g |\0 |    
     +-----+     +---+---+---+---+---+---+---+ 

重要的是要意识到像x[3] 这样的引用会根据x 是数组还是指针生成不同的代码。鉴于上述声明,当编译器看到表达式 a[3] 时,它会发出代码以从位置 a 开始,将三个元素移过它,然后从那里获取字符。当它看到表达式 p[3] 时,它会发出代码以从位置 p 开始,在那里获取指针值,将三个元素大小添加到指针,最后获取指向的字符。在上面的示例中,a[3]p[3] 恰好都是字符 l,但编译器以不同的方式到达那里。

来源:comp.lang.c FAQ list · Question 6.2

【讨论】:

我打赌@Sachin 考虑过像const char *p = "string"; p = "another string"; printf("%c", p[3]); 这样的情况(是的,char *p = "string"; 将是一个编译错误)【参考方案6】:

堆栈、堆、数据段(和 BSS)和文本段是进程内存的四个段。定义的所有局部变量都将在堆栈中。使用malloccalloc 动态分配的内存将在堆中。所有的全局变量和静态变量都在数据段中。文本段会有程序的汇编代码和一些常量。

在这 4 个段中,文本段是 READ ONLY 段,而在所有其他三个段中,是 READWRITE

char a[] = "string"; - 此状态将在堆栈中分配 7 个字节的内存(因为局部变量),并将保留所有 6 个字符(s, t, r, i, n, g)加上 NULL 字符(\0)在末尾。

char *p = "string"; - 该语句将在堆栈中分配 4 个字节(如果是 32 位机器)的内存(因为这也是一个局部变量),它将保存值为 "string" 的常量字符串的指针.这 6 字节的常量字符串将在文本段中。这是一个常数值。指针变量p 只是指向那个字符串。

现在a[0](索引可以是0到5)意味着,它将访问堆栈中该字符串的第一个字符。所以我们也可以在这个位置写。 a[0] = 'x'。此操作是允许的,因为我们在堆栈中有 READ WRITE 访问权限。

但是p[0] = 'x' 会导致崩溃,因为我们只有READ 可以访问文本段。如果我们对文本段进行任何写入,则会发生分段错误。

但是您可以更改变量p 的值,因为它是堆栈中的局部变量。如下所示

char *p = "string";
printf("%s", p);
p = "start";
printf("%s", p);

这是允许的。这里我们将存储在指针变量p 中的地址更改为字符串start 的地址(同样start 也是文本段中的只读数据)。如果您想修改 *p 中的值,则意味着使用动态分配的内存。

char *p = NULL;
p = malloc(sizeof(char)*7);
strcpy(p, "string");

现在p[0] = 'x' 操作是允许的,因为现在我们在堆中写入。

【讨论】:

很好的解释,唯一我不确定的是,字符串文字会存储在文本段还是数据段中? 字符串字面量是只读数据,所以大多数编译器都将其存储在文本段中。 @rajaashok 因此,字符串文字的字节存储在文本段中,这对于每个 C 编译器都是如此?对于 C++?【参考方案7】:

它们在内存存储位置方面确实有所不同。理想情况下,第二个应该使用 const char *。

第一个

char buf[] = "hello";

创建一个足够大的自动缓冲区来保存字符并将它们复制进去(包括空终止符)。

第二个

const char * buf = "hello";

应该使用 const 并简单地创建一个指向内存的指针,该内存通常存储在静态空间中,修改它是非法的。

反过来(事实上你可以安全地修改第一个而不是第二个)是从函数返回第二个指针是安全的,但不是第一个。这是因为第二个将在函数范围之外保持有效的内存指针,第一个不会。

const char * sayHello()

     const char * buf = "hello";
     return buf; // valid


const char * sayHelloBroken()

     char buf[] = "hello";
     return buf; // invalid

【讨论】:

【参考方案8】:

C 和 C++ 具有非常相似的指针到数组的关系...

我无法说出您所询问的两个语句的确切内存位置,但我发现它们的文章很有趣且有助于理解 char 指针声明和 char 数组声明之间的一些差异。

为了清楚起见:

C Pointer and Array relationship

C++ Pointer to an Array

我认为重要的是要记住,在 C 和 C++ 中,数组是指向数组第一个元素的常量指针。因此,您可以对数组执行指针运算。

char *p = "字符串";

以下也是可能的:

char *p;
char a[] = "string";

p = a; 

此时p现在引用a的第一个内存地址(第一个元素的地址)

所以 *p == 's'

*(p++) == 't' 等等。 (或 *(p+1) == 't')

同样的事情也适用于 a:*(a++) 或 *(a+1) 也等于 't'

【讨论】:

以上是关于char a[] = ?string?; 有啥区别?和 char *p = ?string?;?的主要内容,如果未能解决你的问题,请参考以下文章

C#中String和string有啥区别?

C#中string()和ToString()有啥区别?

mysql中char和varchar有啥区别?

数据库建字段,默认值空和empty string有啥区别

MSVC 中的 char 和 CHAR 有啥区别?

从函数返回 char* 和 char[] 有啥区别? [复制]