【问题标题】:how to understand char * ch="123"?如何理解 char * ch="123"?
【发布时间】:2013-12-12 21:12:42
【问题描述】:

我应该如何理解char * ch="123"

'1'char,所以我可以使用:

char x = '1';
char *pt = &x;

但是我怎么理解char *pt="123"?为什么char *pt可以指向字符串?

pt 的值是"123" 的第一个地址值吗?如果是这样,我如何获得pt指向的字符串的长度?

【问题讨论】:

  • 是的,这是不合逻辑且可怕的。不是你的问题 - 带有空终止符的回归 C 风格字符串。 +1 用于识别指向实际上指向多个字符的 char 的指针的不合逻辑。
  • @MartinJames:这不是特定于 C 风格的字符串,任何指针都具有此功能...
  • @KarolyHorvath 并非每个指针都具有可以寻址字符串文字中的字符的功能。这个问题有几个方面(至少如果你想全面回答的话),其中一些只是关于指针,但有些是关于 C 对“字符串”的怪异处理
  • @KarolyHorvath:只有指向可变大小数据结构的指针才有这个问题。即使那样,只有当您认为“这个问题”无法仅从数据结构的类型和指针中知道数据结构的大小时。例如,您可以将“字符串”定义为字符串的长度,然后定义许多字节的字符数据,这将回避空终止带来的所有可怕问题。

标签: c++ char


【解决方案1】:

这实际上是一个非常好的问题,它是 C 语言中几个奇怪的结果:

1:指向 char (char*) 的指针当然也可以指向 chars 数组中的特定 char。这就是指针算法所依赖的:

// create an array of three chars
char arr[3] = { 'a', 'b', 'c'};
// point to the first char in the array
char* ptr = &arr[0]
// point to the third char in the array
char* ptr = &arr[2]

2:字符串文字 ("foo") 实际上不是 string 本身,而只是一个字符数组,后跟一个空字节。 (所以"foo"实际上等价于数组{'f', 'o', 'o', '\0'}

3:在 C 中,数组“衰减”为指向第一个元素的指针。 (这就是为什么很多人错误地说“C 中的数组和指针没有区别”)。也就是说,当您尝试将数组分配给指针对象时,它会将指针设置为指向数组的第一个元素。所以给定上面声明的数组arr,你可以做char* ptr = arr,和char* ptr = &arr[0]的意思一样。

4:在所有其他情况下,像这样的语法会使指针指向一个右值(松散地说,一个临时对象,你不能获取它的地址),这通常是非法的。 (你不能这样做int* ptr = &42)。但是当你定义一个字符串字面量(比如"foo")时,它不会创建一个右值。相反,它使用静态存储创建 char 数组。您正在创建一个静态对象,该对象是在加载程序时创建的,当然指针可以安全地指向 that

5:字符串字面量实际上需要标记为const(因为它们是静态且只读的),但是因为早期版本的C没有const关键字,您可以省略 const 说明符(至少在 C++11 之前),以避免破坏旧代码(但您仍然必须将变量视为只读)。

所以char* ch = "123" 真正的意思是:

  1. 将char数组{'1', '2', '3', '\0'}写入可执行文件的静态部分(这样当程序加载到内存时,该变量会在内存的只读部分中创建)
  2. 执行这行代码时,创建一个指向该数组第一个元素的指针

作为一个有趣的事实,这与 char ch[] = "123"; 不同,而是意味着

  1. 将 char 数组{'1', '2', '3', '\0'} 写入可执行文件的静态部分(这样当程序加载到内存时,该变量会在内存的只读部分中创建)
  2. 执行这行代码时,在堆栈上创建一个数组,其中包含该静态分配数组的副本。

【讨论】:

  • 为什么 int *pt={1,2,3} 不能工作,而 int pt[]={1,2,3} 可以工作?你能帮忙吗?
  • @jiafu {1,2,3} 可以用来初始化数组,但它不是数组。所以在第二种情况下,您使用它来创建一个名为pt 的数组,其中包含这三个数字。但在第一种情况下,您尝试创建一个指向非对象的指针。这就是字符串文字被特殊处理的地方(参见上面的第 4 点),因为它实际上 创建 数组,而 {1,2,3} 语法没有这样做
  • 自 C++11 起,不再允许省略 const
  • 很棒的答案:但可能会提到或强调const 将使代码变得更好?
  • @MikeSeymour 好点,我添加了一条提到这一点的注释。谢谢。
【解决方案2】:

char* ptr = "123"; 兼容并且几乎等同于char ptr[] = { '1', '2', '3', '\0' };(参见http://ideone.com/rFOk3R)。

在 C 中,指针可以指向一个值或一组连续值。 C++ 继承了这一点。 所以字符串只是一个以'\0'结尾的字符数组(char)。指向char 的指针可以指向char 的数组。

长度由开头和结尾'\0' 之间的字符数给出。 C strlen 的示例为您提供字符串的长度:

size_t strlen(const char * str)
{
    const char *s;
    for (s = str; *s; ++s) {}
    return(s - str);
}

是的,如果最后没有'\0',它会非常失败。

【讨论】:

  • char* ptr = "123"; 不是 char* ptr = { '1', '2', '3', '\0' };
  • 我想你的意思是:it fails horribly if there is no '\0' at the end.5]È╦³û§E;‼∟█«2 ÜP¯@x6²↕I2÷×}I▄P
  • 如果是,char *pt1="123", char pt2[]="123", pt2和pt1一样吗?
  • @BЈовић 是的,它是char ptr[] = { '1', '2', '3', '\0' };,它与 char 兼容。我要澄清一下。
  • char* ptrchar ptr[] 不等效。在这两种情况下,您都可以修改 ptr 指向的值。猜猜如果你修改指向 const char 数组的非 const char 的指针会发生什么:) 像这样:ideone.com/i37oMP
【解决方案3】:

字符串文字是一个由 N 个 const char 组成的数组,其中 N 是包含隐式 NUL 终止符的文字长度。它具有静态存储持续时间,并且它的实现定义了它的存储位置。从这里开始,它与普通数组相同——它衰减为指向其第一个字符的指针——即const char*。你所拥有的在 C++ 中是不合法的(自 C++11 标准开始以来不再是合法的),它应该是 const char* ch = "123";

您可以使用sizeof 运算符获取文字的长度。但是,一旦它衰减为指针,您需要遍历它并找到终止符(这就是 strlen 函数所做的)。

因此,使用const char* ch;,您将获得一个指向常量字符类型的指针,该类型可以指向单个字符或字符数组的开头(或开头和结尾之间的任何位置)。数组可以动态、自动或静态分配,并且可以是可变的或不可变的。

char ch[] = "text"; 之类的内容中,您有一个数组 字符。这是普通数组初始化程序的语法糖(如char ch[] = {'t','e','x','t','\0'};,但请注意,文字仍将在程序开始时加载)。这里发生的是:

  • 分配了具有自动存储持续时间的数组
  • 它的大小是由编译器从字面量的大小推导出来的
  • 文字的内容被复制到数组中

因此,您有一个可以随意使用的存储区域(与不能写入的文字不同)。

【讨论】:

  • 如果是,char *pt1="123", char pt2[]="123", pt2和pt1一样吗?
  • @jiafu 不,不是。我将在答案中对此进行扩展,等等。
【解决方案4】:

C 中没有字符串,但有指向字符的指针。 *pt 确实不是指向字符串,而是指向单个字符('1')。 但是,有些函数将char* 作为参数,假设其参数指向的地址后面的地址字节如果不对其进行操作,则设置为0

在您的示例中,如果您尝试在需要“空终止字符串”的函数上使用 pt(基本上,它希望在应该停止处理数据时遇到值为 0 的字节)您将遇到分段错误,因为x='1'x 提供1 字符的ascii 值,仅此而已,而char* pt="123" 给pt 提供1 的地址值,但也放入该内存,包含 123 的 ascii 值的字节后跟一个值为 0(零)的字节。

因此内存(在 8 位机器中)可能如下所示:

地址 = 内容(0x31 是字符 1(一)的 Ascii 代码)

0xa0 = 0x31
0xa1 = 0x32
0xa2 = 0x33
0xa3 = 0x00

假设你在同一台机器上char* otherString = malloc(4),假设malloc返回值0xb0,现在是otherString的值,我们想复制我们的“pt”(将0xa0) 的值转换为otherString,则strcpy 调用如下所示:

strcpy( otherString, pt );

同理

strcpy( 0xb0, 0x0a );

strcpy 然后将地址0xa0 的值复制到0xb0,它将指向“pt”的指针增加到0xa1,检查0xa1 是否为零,如果它不为零,它将增加指向“otherString”的指针并将0xa1复制到0xb1,依此类推,直到“pt”指针为0xa3,在这种情况下,它将返回为它检测到已到达“字符串”的末尾。

这是有原因的,而不是 100% 是如何进行的,它可以通过多种不同的方式实现。

这是一个http://fossies.org/dox/glibc-2.18/strcpy_8c_source.html

【讨论】:

    【解决方案5】:

    指向数组的指针?

    一个指针只指向一个内存地址。指针指向数组这句话只是在广义上使用——一个指针不能真正同时存储多个地址。

    在您的示例char *ch="123" 中,指针ch 实际上只指向第一个字节。您可以编写如下代码,这将非常有意义:

    char *ch = new char [1024];
    sprintf (ch, "Hello");    
    delete [] ch;
    
    char x = '1';
    ch = &x;
    

    请注意使用指针ch 来指向new char [1024] 行分配的内存以及变量x 的地址,同时仍然是相同的指针类型。

    C 风格的字符串以 null 结尾

    C 中的字符串过去是空终止的,即,特殊的 '\0' 被添加到字符串的末尾,并假定所有基于 char * 的函数都存在(例如 strlenprintf)这样,您可以从第一个字节开始确定字符串的长度,直到找到包含0x00 的字节。

    strlen 样式函数的详细示例实现将是

    int my_strlen (const char *startAddress)
    {
      int count = 0;
      char *ptr = startAddress;
      while (*ptr != 0)
      {
         ++count;
         ++ptr;
      }
    
      return count;
    }
    

    【讨论】:

    • 如果是,char *pt1="123", char pt2[]="123", pt2 和 pt1 一样吗?
    • 几乎相同,但不完全相同。如果在我的第一个示例中将char *ch 替换为char ch[],则后面的分配ch = &x 将失败。另请参阅stackoverflow.com/questions/1335786/…
    【解决方案6】:
    char* pt = "123"; does two things:
    

    1。在 ROM 中创建字符串文字“123”(这通常在 .text 部分中) 2. 创建一个char*,分配字符串所在内存位置的开头。

    因为此操作like pt[1] = '2'; 是非法的,因为您将尝试写入 ROM 内存。

    但是你可以毫无问题地将指针分配到其他内存位置。

    【讨论】:

    • 如果是,char *pt1="123", char pt2[]="123", pt2和pt1一样吗?
    • 不,它不是 char p2[] ="123";有不同的效果。字符串文字“123”仍然在 ROM 中创建,但它也被复制到堆栈中,并且 p2 指向堆栈副本。所以 p2[0] = '1' 是合法的。
    • 谢谢,还有,为什么 int *pt={1,2,3} 不能工作,而 int pt[]={1,2,3} 可以工作?你能帮忙吗?
    • for *pt={1,2,3},*pt 指向 ROM 中的一个位置,而对于 pt[]={1,2,3},pt 指向堆栈上的一个位置(可以写的地方)
    猜你喜欢
    • 2013-12-09
    • 1970-01-01
    • 2019-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-19
    相关资源
    最近更新 更多