【问题标题】:Use of pointers on strings在字符串上使用指针
【发布时间】:2018-02-13 20:59:17
【问题描述】:

我对在字符串上使用指针感到非常困惑。 感觉他们遵守不同的规则。 考虑以下代码

  1. char *ptr = "apple";// perfectly valid here not when declaring afterwards like next
    
    ptr = "apple"; // shouldn't it be *ptr = "apple"
    
  2. 另外printf() 的行为也不同 -

    printf("%s", ptr) // Why should I send  the address instead of the value
    
  3. 我还在一本书中看到了以下代码

    char str[]="Quest";
    char *p="Quest";
    
    str++; // error, constant pointer can't change
    
    *str='Z'; // works, because pointer is not constant
    
    p++; // works, because pointer is not constant
    
    *p = 'M'; // error, because string is constant
    

我不明白应该暗示什么

请帮忙,我在其他任何地方都找不到任何信息

【问题讨论】:

  • c 中没有字符串类型。因此,如果有意义的话,您不能将“值”发送给 printf。您只能将第一个地址发送到字符数组。
  • 为什么不放 *ptr ?
  • "我为什么要发送地址而不是值" --> 考虑字符串是 100 秒还是数百万字节长。将字符串的起始地址而不是值传递给函数当然更容易。

标签: c c-strings char-pointer


【解决方案1】:

“*”与指针一起使用时,表示获取指针所指向的内容,在以下情况下:

    char* ptr;

ptr 是一个指向字符的指针,你可以像这样将它分配给一个字符串:

   const char* ptr = "test";

内存中的布局是“t”,然后是“e”、“s”、“t”,最后是一个 nul 终止符 '\0'。

当您像上面那样分配 ptr 时,它会将指针分配给恰好是“t”的第一个内存位置。

*ptr 返回 ptr 指向的内容,并且始终是它声明的类型的大小,在本例中为“char”,单字节。

*(++ptr) 将返回“e”,因为 ptr 在返回它现在指向的内容之前递增到下一个位置。

【讨论】:

    【解决方案2】:

    ptr = "apple"; // shouldn't it be *ptr = "apple"

    从头开始……

    字符串字面量"apple" 存储在char 的6 元素数组中,如下所示:

    +---+---+---+---+---+---+
    |'a'|'p'|'p'|'l'|'e'| 0 |
    +---+---+---+---+---+---+
    

    结尾的0 标志着字符串的结束(它被称为字符串终止符)。

    当“N-element array of T”类型的表达式出现在表达式中时,它会被转换(“decay”)为“pointer to T”类型的表达式和表达式的值将是数组第一个元素的地址,除非数组表达式是sizeof 或一元& 运算符的操作数,或者用于在声明中初始化字符数组。

    因此,在声明中

    ptr = "apple";
    

    表达式"apple"从“char的6元素数组”类型的表达式转换(“衰减”)为“指向char的指针”。表达式ptr的类型是char *,或者“指向char的指针”;因此,在上面的赋值中,ptr 将收到"apple" 的第一个元素的地址。

    应该写成

    *ptr = "apple";
    

    因为表达式 *ptr 的计算结果是 ptr 指向的事物的值,此时它是 a) 不确定的,并且 b) 分配的类型错误。表达式*ptr的类型为char,与char *不兼容。

    我编写了一个实用程序,可以打印内存中的项目映射;给定代码

    char *ptr = "apple";
    char arr[] = "apple";
    

    地图看起来像这样:

           Item         Address   00   01   02   03
           ----         -------   --   --   --   --
          apple        0x400c80   61   70   70   6c    appl
                       0x400c84   65   00   70   74    e.pt
    
            ptr  0x7fffcb4d4518   80   0c   40   00    ..@.
                 0x7fffcb4d451c   00   00   00   00    ....
    
            arr  0x7fffcb4d4510   61   70   70   6c    appl
                 0x7fffcb4d4514   65   00   00   00    e...
    

    字符串文字 "apple" 位于地址 0x400c801。变量ptrarr 分别位于地址0x7fffcb4d45180x7fffcb4d45102

    变量ptr 包含值0x400c80,它是"apple" 字符串文字的第一个元素的地址(x86 以“小端”顺序存储多字节值,因此最不重要字节在前,这意味着您必须从右到左阅读)。

    还记得上面的“except”子句吗?在第二个声明中,字符串文字 "apple" 用于在声明中初始化 chararray;字符串文字的 contents 不是被转换为指针值,而是被复制到数组中,您可以在内存转储中看到。

    1. printf("%s", ptr) // Why should I send the address instead of the value

    因为这是 %s 转换说明符所期望的 - 它需要一个指向以 0 结尾的字符串的第一个字符的指针,并将打印出从该位置开始的字符序列,直到它看到终结者。

    3 ...我不明白应该暗示什么

    您不能更改数组对象的值。让我们看看str 在内存中的样子:

         +---+
    str: |'Q'| str[0]
         +---+
         |'u'| str[1]
         +---+
         |'e'| str[2]
         +---+
         |'s'| str[3]
         +---+
         |'t'| str[4]
         +---+
         | 0 | str[5]
         +---+ 
    

    您可以写入每个str[i]3(更改其值),但您不能写入str,因为没有可写入的内容。没有与数组元素分开的 str 对象。尽管 表达式 str 将“衰减”为指针值,但没有为该指针留出任何存储空间 - 转换是在编译时完成的。

    同样,尝试修改字符串文字的内容会调用未定义的行为4;您可能会遇到段错误,您的代码可能会按预期工作,您可能会在列支敦士登发射核武器。所以你不能写信给*pp[i];但是,您可以将新值写入p,将其指向不同的位置。


    1. 技术上是0x0000000000400c80%p 说明符去掉前导零。
    2. 同样的交易 - 从技术上讲,值是 0x000000007fffcb4d45180x000000007fffcb4d4510。请注意,特定的地址值将随运行而变化。
    3. *str 等价于 str[0]
    4. C 语言定义标识了某些错误的操作,但并未对编译器提出任何要求以任何特定方式处理该代码。不同的平台以不同的方式存储字符串文字;有些将它们放在只读内存中,因此尝试修改它们会导致段错误,而其他平台将它们存储在可写段中以便操作成功。有些人可能会以不会出现段错误的方式存储它们,但字符串不会更改。

    【讨论】:

      【解决方案3】:

      1- 我认为您对变量声明和定义有些混淆。这一行:

      char *ptr = "apple";
      

      声明一个指向char的指针并将第一个字符“a”的地址分配给变量ptr。这行相当于下面的2:

      char* ptr;
      ptr = "apple";
      

      现在,C 中的字符串文字是只读的。它们是隐式不变的,和做的一样

      const char* ptr;
      

      所以事实上,你不能改变这个指针指向的位置的内容。现在,即使你可以,你做的方式也是错误的。因为 ptr 指向字符串的第一个字符的位置,所以当您执行 *ptr 时,您正在访问该字符串的第一个字符的内容。所以它需要一个字符,而不是字符串。所以它会是这样的:*ptr = 'a';

      2- 嗯,这就是 printf 的工作方式。如果要打印带有 %s 说明符的字符串,它需要一个指向该字符串的指针,即字符串第一个字符的地址,而不是字符串的值本身。

      3- 现在我要评论您的代码。

      str++; // error, constant pointer can't change
      

      你是对的。其他人一直说数组和指针略有不同,但事实并非如此。数组只是程序员的一种抽象,表示您正在存储一系列值。在装配级别,根本没有区别。您可以说数组是具有可变内容的不可变指针。数组存储值序列的第一个元素的地址。可以更改数组的内容,但不能更改地址(它指向的第一个元素)。

      *str='Z'; // works, because pointer is not constant
      

      现在你搞错了。指针实际上是常量,也就是说,你不能改变它存储的地址。但是您可以更改地址指向的内容,这就是上面的行所做的。它正在更改数组中值序列的第一个值。

      p++; // works, because pointer is not constant
      

      正确。指针不是常量,尽管它指向的内容是常量。您可以更改指针存储的地址,但不能更改它指向的值。字符串字面量是指向不可变字符串的可变指针。

      *p = 'M'; // error, because string is constant
      

      正确,字符串是不可变的。

      【讨论】:

        【解决方案4】:
        char *ptr;
        ptr = "apple"; // shouldn't it be *ptr =      "apple"
        

        不,因为*ptr 将是char。所以,你可以写*ptr = 'a',但不能按照你的建议写。

        printf("%s", ptr) // Why should I send  the address instead of the value
        

        因为 C 中的字符串,是以零结尾的字符序列 (char) 的地址(空字符又名 \x0)。

        char str[] = "Quest";
        char *p = "Quest";
        
        str++; // error, constant pointer can't change
        

        不,指针可以完美改变,但这里,str 是一个数组(与指针略有不同)。但是,因此,它不能处理指针运算。

        *str='Z'; // works, because pointer is not constant
        

        不,它有效,因为 *str 应该是 char

        p++; // works, because pointer is not constant
        

        不,它有效,因为这一次,这是一个指针(不是数组)。

        *p = 'M'; // error, because string is constant
        

        和上面一样,这又是一个char,所以它之所以有效是因为它是正确的类型,而不是因为字符串是“常量”。并且,正如 Michael Walz 在 cmets 中所说,即使它可以编译,它也会在运行时产生未定义的行为(很可能会导致 segfault 崩溃),因为规范没有说明 *p 指向的字符串是否为只读与否(然而,似乎大多数现代编译器实现都决定将其设为只读)。这可能会产生segfault

        更多信息,请参考this SO question

        【讨论】:

        • 最好使用NULL(大写)作为指针上下文中使用的空指针常量。建议使用“null character aka '\0')”来匹配 C 规范的风格。
        • 我知道我在写的时候写了一些奇怪的东西……我完全同意。
        • *p = 'M' 应该根据你的需要工作,因为它是一个字符
        • 是的,但我的意思是“与上面相同...”以匹配处理*p 的情况。不是,“同上……”只是上面一行……嗯,确实不太清楚……那我重写最后一句。
        • @perror 仍然不太清楚。您应该清楚 p 是指向字符串文字的指针,并且修改字符串文字会在 UB 中产生。但是*p = 'M' 在语法上当然是正确的。您应该区分编译错误和运行时错误。 str++; 不会编译。 *p = 'M' 编译但会在运行时在 UB 中产生(很可能在现代系统上崩溃)。
        【解决方案5】:

        我只会回答子问题 1。但是您已经触及 C 语言中一个常见但微妙的混淆,即初始化指针的方式与分配给该指针的方式之间存在轻微的不匹配。仔细观察。

        如果我有一个int 变量,我可以在声明它时对其进行初始化:

        int i = 42;
        

        或者,我可以在一行上声明它(不初始化它),然后给它一个值:

        int i;
        i = 42;
        

        这并不神秘。但是当涉及到指针时,它看起来有点不同。同样,我可以在一行上声明和初始化:

        char *ptr = "apple";
        

        或者我可以拆分声明和赋值:

        char *ptr;
        ptr = "apple";
        

        但是,一开始这看起来很奇怪——基于第一种语法,第二种方式不应该是这样的吗?

        *ptr = "apple";         // WRONG
        

        不,不应该,原因如下。

        ptr 是指向某些字符的指针。这是在 C 中引用字符串的一种方式。

        * 是指针间接运算符。在表达式中,*ptr 指的是ptr 指向的字符(只是一个字符)。因此,如果我们想获取字符串的第一个字符,我们可以使用* 来做到这一点:

        printf("first character: %c\n", *ptr);
        

        请注意,此printf 调用中的格式使用%c,因为它只打印一个字符。

        我们也可以分配指针。如果我们使用指向 char 的指针,并且因此将这些指针视为“字符串”,那么这是在 C 中进行字符串赋值的一种方式。如果我说

        ptr = "apple";
        

        那么无论ptr 过去指向哪里,现在它都指向一个包含字符串“apple”的字符数组。如果我以后说

        ptr = "pear";
        

        那么ptr 不再指向字符串“apple”;现在它指向包含字符串“pear”的不同字符数组。你可以把这个指针想象成一次分配字符串的所有字符(尽管它实际上根本不是这样做的)。

        所以如果*ptr 只访问一个字符,而ptr 本身就是指针值,那为什么第一种形式呢

        char *ptr = "apple";
        

        工作?

        答案是当你说

        char *ptr = "apple";
        

        其中显示的* 不是指针间接运算符。这并不是说我们正在尝试访问任何内容的第一个字符。

        当你说

        char *ptr = "apple";
        

        * 表示ptr 是一个指针。就像你说的那样

        char *ptr;
        

        * 表示ptr 是一个指针。

        C' 指针的声明语法有点奇怪。这是如何考虑的。语法是

        type-name thing-that-has-that-type ;

        所以当我们说

        char *ptr;
        

        type-namecharthing-that-has-that-type*ptr。 我们说*ptr 将是char。如果*ptr 将是char,则意味着ptr 必须是指向char 的指针。

        然后,当我们说

        char *ptr = "apple";
        

        我们是说ptr(我们刚刚说的是指向char 的指针)应该有一个指向包含字符串“apple”的数组的指针作为其初始值。

        【讨论】:

          【解决方案6】:
          1. "SOME STRING" 在内存中创建一个以\0 结尾的字符序列并返回其第一个字符地址,以便您可以将其分配给一个指针:
            char *ptr = "Hello";

          2. printf 函数也适用于地址,类型说明符定义它应该如何从内存中读取数据。

          3. char str[]="Quest"; char *p="Quest";
            在第一个中,您正在创建一个包含 6 个房间的数组并在其中存储 'Q', 'u', 'e', 's', 't', '\0',然后您可以通过 str[2] = 'x' 更改一些索引值,但数组名称本身是一个常量,其地址指向它指向的第一个位置所以你不能用str++;
            之类的东西来改变它 但是在第二个"Quest\0" 是一个常量字符串,它保存在内存中的某个位置,它的第一个内存位置存储在p,所以你不能改变它,但指针本身不是const,你可以做@ 987654332@.

          【讨论】:

            猜你喜欢
            • 2021-07-30
            • 1970-01-01
            • 1970-01-01
            • 2020-10-30
            • 2012-05-05
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多