【问题标题】:Why uninitialised pointer variable as a string causes error?为什么未初始化的指针变量作为字符串会导致错误?
【发布时间】:2020-05-12 20:39:15
【问题描述】:
#include <stdio.h>

int main()
{     
     char *p;         
     p[0]='a';
     p[1]='b';
     p[2]='c';
     p[3]='d';
     p[4]='\0'; 
}

上面的程序和下面的程序有什么不同?

#include <stdio.h>

int main()
{
      char *p = "abcd";
}

【问题讨论】:

  • 您取消引用指向无处的指针,您很困惑问题是什么?始终能够识别指针指向的有效存储(即,指针变量将地址作为其值保存)您尝试取消引用指针之前。 [..] 就像 '*' 一样作为取消引用运行......
  • 第一个程序是UB,因为p的内存没有分配,第二个程序分配了它
  • 问:上面的程序和下面的程序有什么不同?答:第一种情况为指针分配内存ONLY(例如,32 位 CPU 为 4 个字节,64 位 CPU 为 8 个字节)。它不会为任何元素分配 ANY 内存,也不会将指针初始化为任何有意义的地址。
  • 第二个程序指向基地址,即“a”,但为什么不是第一个程序我明确编写了代码 p[0] 指向“a”,这与第二个程序相同或如果我错了,请纠正我为什么第二个程序以它的工作方式工作,为什么不是第一个程序
  • 一些提供指针基本讨论的链接可能会有所帮助。 Difference between char pp and (char) p?Pointer to pointer of structs indexing out of bounds(?)...

标签: c arrays string pointers char


【解决方案1】:

在 C 中,指针本身指向的对象之间是有区别的,而您上面显示的代码示例正是针对这种区别的。

让我们从第二个代码示例开始:

char* p = "abcd";

在这里,它执行以下操作。首先,它在内存中放置一个包含字符串"abcd" 的五个字符的数组。看起来像这样:

+---+---+---+---+---+
| a | b | c | d | \0|
+---+---+---+---+---+

接下来,它创建指针p,并告诉p 指向该数组中的第一个字符:

+-----+
|     | p
+-----+
   |
   v
+---+---+---+---+---+
| a | b | c | d | \0|
+---+---+---+---+---+

此时,宇宙中一切都很好。

现在,将其与您编写的另一段代码进行对比。第一行char* p 创建了一个指针p,但没有告诉它指向哪里。结果,它随机指向某个地方,并且可能指向您不拥有的内存:

  +-----+
p |     | -----> ???
  +-----+

现在,如果你写p[0] = a,你的意思是“请到p 指向的地方写a”。不幸的是,p 并没有指向你拥有的任何东西,所以这相当于说“我知道我实际上并没有将这支枪瞄准任何合理的方向,但让我们扣动扳机看看会发生什么反正。”你很容易伤害别人,可能是你自己!

要解决此问题,您需要创建指针 p 并告诉它您希望它指向的位置。您可以使用malloc 为自己获取一块内存,或使用strdup 获取要指向的字符串的副本,或者如果您愿意,也可以使用字符串文字。

今后,绘制这样的图片对于理解代码的作用非常有帮助。

【讨论】:

    【解决方案2】:

    下面的代码是正确的,但上面的代码是不正确的。 原因是当你定义指针时,你应该指出内存位置。 例如, 字符 *p; // 这个 p 指针不指示任何内存位置。 因此,当您尝试使用 p[0]='X' 时,编译器不知道将其保存在哪里。 也许它会引发核心转储错误。 在这种情况下,您应该这样做。 char *p = (char *)malloc(255 * sizeof(char)); //分配内存 memset(p, 0, 255); // 用零数据初始化内存 然后您的第一个代码将起作用。 (不要忘记包含 stdlib.h)

    希望对你有所帮助。

    【讨论】:

    • 我忘了解释第二个代码 sn-p。字符 *p = "abcd"; // 它表示内存中的“abcd”字符串。它不分配内存。所以它有效
    【解决方案3】:

    在 C 中,char *p; 是指向 char 的指针的声明,当您不初始化变量时,编译器不会进行内存分配。

    下面的代码只是声明了一个指针,它和int *i;没有什么不同。你是在告诉你的编译器p 指向一个存储char 的内存地址。

    char *p; /* pointe to a char, that's it */
    

    而在下面的代码 sn-p 中,您初始化了内存(只读)。与上面的示例类似,p 仍然是指向char 的指针,但p 之后的内存位置(即p+1p+2,...)现在也分配给字符。这是由您的编译器完成的,但分配是只读的。

    char *p = "Hello"; /* pointer to a char but the memory address */
                       /* it is pointing to has been initialised   */
    

    查看您在 OP 中的代码 sn-p

    #include <stdio.h>
    
    int main()
    {     
         char *p;         
         p[0]='a';
         p[1]='b';
         p[2]='c';
         p[3]='d';
         p[4]='\0'; 
    }
    

    虽然直觉可能会暗示,这

         char *p;         
         p[0]='a'; /* error here */
    

    不一样

         char *p, c='a';         
         p=&c;
    

    前者要求您有一个已初始化(分配)的内存。在 C 中,您只能在声明字符指针时为其分配一个常量值。即

         char *p="a";  /* valid operation */
    

    如果指向 char 的指针在声明时未初始化,则以后不能再进行初始化。从那时起,您可以执行的唯一有效的赋值操作是将 char 变量的地址分配给指针。就是这样。

    让我们看看您的编译器如何解释两个操作并从那里得出结论。拿这个玩具程序

    int main() {
        char c, *p, *str = "Hello";
        c = 'a';
        p = &c;
        p[0] = 'a';
    }
    

    没有任何优化,clang 发出下面的代码

    main:                                   # @main
            push    rbp                        ; function prologu
            mov     rbp, rsp                   ; function prologu
            xor     eax, eax
            movabs  rcx, offset .L.str         ; *str = "Hello"
            mov     qword ptr [rbp - 24], rcx
            mov     byte ptr [rbp - 1], 97     ; c = 'a'
            lea     rcx, [rbp - 1]             ; p = &c
            mov     qword ptr [rbp - 16], rcx  ; p = &c
            mov     rcx, qword ptr [rbp - 16]  ; p[0]='a'
            mov     byte ptr [rcx], 97         ; p[0]='a'
            pop     rbp
            ret
    .L.str:
            .asciz  "Hello"
    

    LEA instruction 将第二个操作数的有效地址加载到第一个操作数中。在上面的代码中,内存位置rbp-1 是为变量c 保留的,因此它的值是97,这是a 的ASCII 码。而位置rbp-16 是指针p

    下面的代码

    int main() {
        char c, *p, *str = "Hello";
        c = 'a';
        p = &c;
        p=malloc(1); /* added a malloc() here */
        p[0] = 'a';
    }
    

    下面是汇编代码

    main:                                   # @main
            push    rbp                        ; function prologue
            mov     rbp, rsp                   ; function prologu
            sub     rsp, 32                    ; function prologu
            movabs  rax, offset .L.str         ; *str = "Hello"
            mov     qword ptr [rbp - 24], rax
            mov     byte ptr [rbp - 1], 97     ; c = 'a'
            lea     rax, [rbp - 1]             ; p = &c
            mov     qword ptr [rbp - 16], rax  ; p = &c
            mov     edi, 1                     ; malloc(1)
            call    malloc                     ; malloc(1)
            xor     ecx, ecx
            mov     qword ptr [rbp - 16], rax  ; malloc(1)
            mov     rax, qword ptr [rbp - 16]  ; p[0]='a'
            mov     byte ptr [rax], 97         ; p[0]='a'
            mov     eax, ecx
            add     rsp, 32
            pop     rbp
            ret
    .L.str:
            .asciz  "Hello"
    

    在执行p[0]='a'时,两个代码sn-ps执行相同的指令,只是使用不同的寄存器来存储p指向的地址。但是,p[0]='a' 行之前的 malloc() 调用会产生很大的不同。 p 指向的内存位置现在由操作系统分配,分配给该内存地址的操作是有效的。

    如果没有malloc() 调用,您只是试图写入未初始化的内存,您还没有从操作系统请求那块内存。尽管说明看起来相同,但您的操作系统看起来却不同。

    【讨论】:

    • 我知道 char *p 没有初始化为任何变量,这是它导致错误的原因,但后来我明确地将 char *p 初始化为 'a' 等等。我的观点是如果我们稍后将 char *p 初始化为 'a' 那么为什么它会给出错误
    • 查看我的编辑; TL;DR - 您只能在声明 char 指针时对其进行初始化,否则唯一有效的赋值操作是将 char 变量的地址分配给指针。
    【解决方案4】:

    不同之处在于,在第一个示例中,您尝试使用 未初始化的指针(无处)来更改它指向的带有偏移量的值(无处),这就是您的程序崩溃的原因。

    第二个例子,你的字符串"abcd"会在编译时作为literal存储在你程序的特殊数据段中,并标记为read-only(常数)。当然这个文字有地址,你的指针 p 会在执行时得到这个地址。 more details

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-09-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-20
      • 2019-07-01
      相关资源
      最近更新 更多