使用指针 Vs 定义字符串。 C 中的字符数组

Defining strings using pointers Vs. char arrays in C

我对字符指针的工作原理感到困惑。当我 运行 以下代码时,会发生什么?

int main()
{
    char* word;
    scanf("%s",word);
    printf("%s",word;
}

main 中的第一行定义了一个没有初始化的指向 char 的指针。 scanf 应该将单词存储在某处并将地址提供给指针,对吗?如果我输入一个大字符串,它会覆盖内存中的东西吗?

除了定义指向 char 的指针之外,以下代码的第一行发生了什么。编译器是否设置了一些限制?或者我不能超过指定的尺寸,对吗?如果完成,我将出现 运行 时间错误,对吗?这两种情况有什么区别?

int main()
{
    char word[100];
    scanf("%s",word);
    printf("%s",word;
}

指向其他类型的指针呢?我可以使用偏移量继续写入以下位置吗?

scanf should store the word somewhere and give the address to the pointer, right?

没有。恰恰相反。您定义 scanf 存储值的地址。当您无法将指针初始化为某个有效地址时,您会导致未定义的行为,在最好的情况下可能会导致崩溃,或者在最坏的情况下似乎可以正常工作。

And what happens in the first line in the following code other than defining a pointer to char.

根本不涉及指针。数组不是指针。数组提供了存储其所有成员所需的所有内存。指针不会这样做。

Does the compiler set some limits? or I can't exceed the size specified, right?

想写什么就写什么。没有人会阻止你这样做。至少没有尝试。如果您写入某个不属于您分配的内存的位置,您将再次导致未定义的行为。

函数scanf要求您将一个足够大的内存缓冲区的地址传递给它来存储字符串。如果您不这样做,那么您将调用 undefined behavior(即您的程序可能会崩溃)。

简单地传递一个wild pointer (i.e. an arbitrary memory address) is not sufficient. Rather, you must reserve the memory that you intend to use, for example by declaring an array or by using the function malloc

单独使用%s scanf 转换格式说明符不是一个好主意,因为即使分配的内存缓冲区大小为 100 个字符,如果用户键入超过 99 个字符(100,包括终止空字符),那么函数将越界写入数组,导致未定义的行为。因此,您应该始终限制写入的字符数,在这种情况下,通过写入 %99s 而不是简单地 %s.

此外,在使用 scanf 的结果之前,您应该始终检查函数的 return 值,并且只有在函数成功时才使用结果。

int main()
{
    char word[100];
    if ( scanf( "%99s", word ) == 1 )
        printf( "%s\n", word );
    else
        printf( "input error!\n" );
}

what if I input a big string, would it overwrite something in the memory?

它不一定是“大”字符串。即使将一个“小”字符串写入野指针也会导致未定义的行为,并且可能会覆盖重要的内容,或者您​​的程序可能会崩溃。

And what happens in the first line in the following code other than defining a pointer to char. Does the compiler set some limits?

char word[100];

将分配一个 100 个字符的数组,即它会给你一个足够大的内存缓冲区来存储 100 个字符。这不会给你一个指针。但是,在

行中使用数组 word
scanf("%s",word);

数组worddecay指向第一个元素的指针。

Does the compiler set some limits? or I can't exceed the size specified, right?

编译器不会阻止您越界写入数组,但如果您允许这种情况发生,那么您的程序将出现未定义的行为(即您的程序可能会崩溃)。因此,您可能不想让这种情况发生。

If done, I will have a run time error, right?

如果幸运的话,是的,您的程序会立即崩溃,您将能够轻松识别并修复错误。如果你不走运,那么不,你的程序不会崩溃,但会按预期工作,而且你不会注意到这个错误很长一段时间,直到开发的后期,有一天错误开始覆盖一些重要的东西在你的程序中。在那种情况下,错误可能很难诊断。

这是因为 C 不是 memory-safe 语言。

但是,由于此类错误通常很难发现,因此有一些工具可以帮助检测此类错误,例如 valgrind and AddressSanitizer

根据C标准中转换说明符%s的描述

If no l length modifier is present, the corresponding argument shall be a pointer to the initial element of a character array large enough to accept the sequence and a terminating null character, which will be added automatically.

也就是说,当您将指针作为对应于 %s 格式的函数的参数传递时,它应指向将存储输入字符串的字符数组的第一个元素。字符数组应足够大以容纳输入的字符串(包括附加的终止零字符 '[=28=]'

在第一个节目中

int main()
{
    char* word;
    scanf("%s",word);
    printf("%s",word;
}

指针 word 未初始化且具有不确定的值。所以这两个语句

    scanf("%s",word);
    printf("%s",word;

调用未定义的行为。

您需要提供指向字符数组的指针的有效值。例如

char s[100];
char *word = s;

或者你可以像

那样动态分配内存
char *word = malloc( 100 * sizeof( char ) );

在第二个节目中

int main()
{
    char word[100];
    scanf("%s",word);
    printf("%s",word;
}

用作参数的数组word被隐式转换为指向其第一个元素的指针。如果您输入的字符串适合包含 100 个元素的数组,那么程序将正常运行。

但是,如果您要输入 100 个或更多没有嵌入空格的字符,那么程序将再次出现未定义的行为。

为避免这种情况,您可以通过以下方式使用长度修饰符指定数组中可以读取的字符串的最大长度word

    scanf("%99s",word);

如果您想输入可能包含嵌入空格的字符串,您应该使用另一个转换说明符。例如

    scanf("%99[^\n]", word );

    scanf(" %99[^\n]", word );

这里有两个演示程序,显示了用于输入字符串的两个转换说明符之间的区别。

#include <stdio.h>

int main(void) 
{
    char word[100];
    
    scanf( "%99s", word );
    
    puts( word );
    
    return 0;
}

如果要输入字符串

Hello Mohammed Elbagoury

那么程序输出将是

Hello

和第二个节目

#include <stdio.h>

int main(void) 
{
    char word[100];
    
    scanf( "%99[^\n]", word );
    
    puts( word );
    
    return 0;
}

再次进入

Hello Mohammed Elbagoury

那么程序输出将是

Hello Mohammed Elbagoury

如果您要输入多于 99 个字符,则只有前 99 个字符将存储在数组中并附加终止零字符 '[=28=]'.

关于你这个问题

Can I just keep writing to the following places using offsets?

那么就可以使用指针算法将数据存储在数组的任意位置。例如

int a[10];

scanf( "%d", a + 5 );

在这种情况下,将在数组的元素中写入一个数字 a[5]

以上语句等同于

scanf( "%d", &a[5] );