为什么程序只有在我临时给 `char **` 变量赋值时才能运行?

问题描述 投票:0回答:3

我试图在一个函数中分配一个字符串,并通过一个

char **
类型的指针返回它。如果我只是简单地创建一个
char **
类型的指针,将它传递给一个函数并让该函数将值更改为指向字符串的指针,程序编译但返回
Program returned: 139
.

但是,如果我在调用函数之前简单地分配一个值,它就可以工作!

#include <stdio.h>
#include <stdlib.h>

#include<stdbool.h>  // Include bool type


void getString(char **str_ptr){
    char * my_str = "String I passed." ;
    *str_ptr = my_str ;
}

void printMyStr(char **str_ptr){
    int i = 0;
    
    char *str = *str_ptr;

    printf("Print starts \n\t" );
    // looping till the null character is encountered
    while(str[i] != '\0')
    {
        printf("%c", str[i]);
        i++;
    }
    printf("\nPrint ends \n" );
}

int main() {
    
    char *str = "My great String";
    char **str_ptr;

    str_ptr = &str; \\ If this is commented, errors with 139
    getString(str_ptr);

    printMyStr(str_ptr);

    return 0;
};

以上程序返回

0
输出

Print starts 
    String I passed.
Print ends 

但是,如果我注释掉

str_ptr = &str;
行,那么它返回
139
并且什么都不打印。

那么,为什么它会失败,当我评论该行时?

And为什么它适用于那条线?该行如何改变任何东西(但输出的仍然是“我传递的字符串。”)

c string pointers
3个回答
0
投票

如果你注释掉这一行,那么你的程序会:

char **str_ptr;
*str_ptr = "String I passed.";

这与例如没有什么不同。

int *int_ptr;
*int_ptr = 42;

然后它向一个未知的内存地址写入一些东西(我们不知道地址是因为我们不知道指针变量包含什么地址)然后崩溃。

如果你特别倒霉,它会覆盖一些重要的东西(或者至少,和这个愚蠢的小程序中的任何东西一样重要)而不是崩溃。

当您注释该行时,现在您告诉它要将数据写入何处:

char **str_ptr;
str_ptr = &str;
*str_ptr = "String I passed.";

相同
str = "String I passed.";

0
投票

您的程序不会崩溃/调用包含

str_ptr = &str;
的未定义行为,因为它会将其初始化为进程空间内的有效内存地址。

char *str = "My great String";
char **str_ptr;

str_ptr = &str; // *str_ptr now points to "My great String"

所以当你在

getString
中取消引用时,一切都很好

void getString(char **str_ptr){
    // ok to dereference now, because you initizlied it to some valid memory.
    *str_ptr = "String I passed.";
}

也许您在想,“

char str*
中的
main
不存在于
getString
中,那么
*str_ptr
中的
getString
是否会取消引用超出范围的内容?”那里有两件事:

  1. 字符串文字具有静态存储持续时间,因此 它们在整个程序生命周期中保持活跃
  2. 即使对于不同的变量类型,
    *str_ptr
    也是有效的,因为它指向的东西仍然存在。函数的局部变量在函数的右大括号
    }
    之前不会消失。您使用
    str_ptr
    中变量的地址初始化
    main
    ,并且它保持活动状态直到
    main
    退出(在这种情况下,这恰好是程序的生命周期)。

0
投票

我会复制你给的相关行。但首先,我将内联该函数,以便您可以更好地了解发生了什么。函数参数是一个在函数调用之前初始化的 locl 变量,所以我要做的是重命名它,使它成为一个新的局部变量,并像函数调用一样分配它。

所以内联版本看起来像这样:

    char *str = "My great String";
    char **str_ptr;

    str_ptr = &str; \\ If this is commented, errors with 139
    // This gets inlined.
    // getString(str_ptr);
    char **getString_str_ptr = str_ptr;
    char * my_str = "String I passed." ;
    *getString_str_ptr = my_str ;

pretend 参数与

str_ptr
的类型和值相同,现在是多余的,所以我会去掉它。

    char *str = "My great String";
    char **str_ptr;

    str_ptr = &str; \\ If this is commented, errors with 139
    char * my_str = "String I passed." ;
    *str_ptr = my_str ;

当你声明

str_ptr
时,它没有任何价值。我们可以给它任何东西,但我会给它
NULL
以使其清楚。很明显,如果您尝试将任何内容分配给
NULL
,或取消引用
NULL
,它将失败。

    char *str = "My great String";
    char **str_ptr = NULL;

    str_ptr = &str; \\ If this is commented, errors with 139
    char * my_str = "String I passed." ;
    *str_ptr = my_str ;

应该清楚的是,因为你在声明它之后立即分配一个值,这与用赋值替换

NULL
是一样的。

    char *str = "My great String";
    char **str_ptr = &str;

    char * my_str = "String I passed." ;
    *str_ptr = my_str ;

str_ptr
现在指向一个指向 16 字节存储空间的指针。存储并不重要,重要的是
*str_ptr
指向实际内存(
str
的地址),而不是NULL。如果您注释掉该行,代码将如下所示:

    char **str_ptr = NULL;

    char * my_str = "String I passed." ;
    *str_ptr = my_str ;

应该清楚

*str_ptr
试图取消引用
NULL
,这是行不通的。

© www.soinside.com 2019 - 2024. All rights reserved.