繁体   English   中英

添加额外的 int 时 C 程序崩溃

[英]C Program crashes when adding an extra int

我是 C 新手并使用 Eclipse IDE 以下代码工作正常:

#include <stdio.h>
#include <stdlib.h>
#include <String.h>
int main()
{
    char *lineName;
    int stationNo;
    int i;
    while (scanf("%s (%d)", lineName, &stationNo)!=EOF) {
        for (i=0; i<5 ; i++ ){
            printf("%d %d",i);
        }
    }
    return 0;
}

输入:

Green (21)

Red (38)

输出:

Green (21)

Red (38)
0123401234

但是,当简单地添加一个新的 int 时:

#include <stdio.h>
#include <stdlib.h>
#include <String.h>
int main()
{
    char *lineName;
    int stationNo;
    int i,b=0;
    while (scanf("%s (%d)", lineName, &stationNo)!=EOF) {
        printf("%d",b);
        for (i=0; i<5 ; i++ ){
            printf("%d",i);
        }
    }
    return 0;
}

程序将因相同的输入而崩溃。 谁能告诉我为什么?

你说你的第一个程序“有效”,但它只是偶然的。 这就像一辆汽车在前轮上没有拉着螺母的情况下在路上快速行驶,只是奇迹般地它们还没有掉下来 - 还没有。

你说

char *lineName;

这为您提供了一个可以指向某些字符的指针变量,但它尚未指向任何地方 此指针的值未定义。 这有点像说“ int i ”并询问i的值是什么。

接下来你说

scanf("%s (%d)", lineName, &stationNo)

您要求scanf读取行名称并将字符串存储在lineName指向的内存中。 但是那个记忆在哪里? 我们什么都不知道!

考虑未初始化指针的情况有点棘手,因为与往常一样,对于指针,我们必须区分指针的值与指针指向的内存中的数据 前面我提到说int i和询问的价值是什么i是。 现在, i会有一些位模式——它可能是 0、1、-23 或 8675309。

同样, lineNamelineName有一些位模式——它可能“指向”内存位置 0x00000000、0xffe01234 或 0xdeadbeef。 但接下来的问题是:那个位置实际上是否有任何内存,我们是否有权写入它,它是否被用于其他用途? 如果有内存并且我们确实有权限并且它没有被用于其他任何事情,那么该程序似乎可以工作——目前。 但那是三个相当大的ifs! 如果内存不存在,或者如果我们没有写入权限,程序可能会在尝试时崩溃。 如果内存被用于其他用途,那么当我们要求scanf在那里写入它的字符串时,就会出现问题——如果不是现在,那么以后。

而且,真的,如果我们关心的是编写有效的程序(并且出于正确的原因而有效),我们就不必问任何这些问题。 当我们不初始化lineName时,我们不必询问lineName指向哪里,或者那里是否有任何内存,或者我们是否有权写入它,或者它是否被用于其他用途。 相反,我们应该简单地,实际上,初始化lineName 我们应该明确它指向的内存,我们自己的,我们允许写和被用于其他任何东西!

有几种方法可以做到这一点。 最简单的方法是对lineName使用数组,而不是指针:

char lineName[20];

或者,如果我们决定使用指针,我们可以调用malloc

char *lineName = malloc(20);

但是,如果我们这样做,我们必须检查以确保malloc成功:

if(lineName == NULL) {
    fprintf(stderr, "out of memory!\n");
    exit(1);
}

如果您进行其中任何一项更改,您的程序就会运行。

...嗯,实际上,我们仍然处于您的程序似乎可以工作的情况,即使它还有另一个非常严重的潜在问题。 我们为lineName分配了 20 个字符,这给了我们 19 个实际字符,加上尾随'\\0' 但是我们不知道用户要输入什么。 如果用户键入 20 个或更多字符怎么办? 这将导致scanf将超过 20 个字符写入lineName ,超过lineName的内存允许保留的末尾,我们又回到了写入我们不拥有的内存的情况,这可能是用于其他用途。

一种解决方案是使lineName更大——将其声明为char lineName[100] ,或调用malloc(100) 但这只是解决了问题——现在我们不得不担心(可能更小)用户输入 100 个或更多字符的可能性。 所以接下来要做的是告诉scanf不要向lineName写入比我们安排的更多的内容。 这实际上很简单。 如果lineName仍然设置为容纳 20 个字符,只需调用

scanf("%19s (%d)", lineName, &stationNo)

格式说明符%19s告诉scanf它只允许读取和存储最多 19 个字符长的字符串,留出一个字节用于终止'\\0'它还将添加。


现在,我在这里说了很多,但我意识到我实际上还没有来得及回答这个问题,当你做出看似微不足道的、看似无关的改变时,你的程序为什么会从工作变成崩溃。 这最终成为一个很难令人满意地回答的问题。 回到我开始回答这个问题时的比喻,这就像问为什么你能把没有 lugnuts 的车开到商店没有问题,但是当你试图开车去奶奶家时,车轮掉了,你撞上了一条沟。 有一百万种可能的因素可能会起作用,但它们都没有改变这样一个基本事实,即在车轮未系紧的情况下驾驶汽车是一个疯狂的想法,这根本不能保证有效。

在您的情况下,您正在谈论的变量 -- lineNamestationNoib -- 都是局部变量,通常分配在堆栈上。 现在,堆栈的特征之一是它被用于各种东西,并且在使用之间永远不会被清除。 因此,如果您有一个未初始化的局部变量,它最终包含的特定随机位取决于上次使用该堆栈的内容。 如果您稍微更改程序以调用不同的函数,这些不同的函数可能会在堆栈上留下不同的随机值。 或者,如果您更改函数以分配不同的局部变量,编译器可能会将它们放在堆栈上的不同位置,这意味着它们最终会从上次存在的任何值中获取不同的随机值。

无论如何,不​​知何故,在您的程序的第一个版本中, lineName最终包含一个随机值,该值对应于指向实际内存的指针,您可以通过写入而逃脱。 但是,当您添加第四个变量b ,事情发生了足够的变化, lineName最终指向了不存在或您无权写入的内存,并且您的程序崩溃了。

有道理吗?


现在,还有一件事,如果你还在我身边。 如果你停下来想一想,整个事情可能会令人不安。 你有一个程序(你的第一个程序)看起来运行得很好,但实际上有一个相当可怕的错误。 它写入随机的、未分配的内存。 但是当你编译它时,你没有得到致命的错误信息,当你运行它时,没有任何迹象表明有任何问题。 那是怎么回事?

正如一些评论所提到的,答案涉及我们所说的未定义行为

事实证明,存在三种 C 程序,我们可以称之为好的、坏的和丑陋的。

  • 好的程序有正确的理由。 他们不违反任何规则,他们不做任何违法的事情。 当您编译它们时,它们不会收到任何警告或错误消息,而当您运行它们时,它们就可以正常工作。

  • 坏程序破坏了某些规则,编译器会捕捉到这一点,并发出致命错误消息,并拒绝生成破坏程序供您尝试运行。

  • 但是还有一些丑陋的程序,它们从事未定义的行为 这些是违反一组不同规则的规则,由于各种原因,编译器没有义务抱怨这些规则。 (实际上,编译器可能会也可能不会检测到它们)。 从事未定义行为的程序可以做任何事情

让我们再考虑一下最后一点。 当您编写使用未定义行为的程序时,编译器没有义务生成错误消息,因此您可能没有意识到自己已经这样做了。 并且该程序可以做任何事情,包括您期望的工作。 但是,既然它被允许做任何事情,它可能会在明天停止工作,似乎没有任何理由,要么是因为你对它做了一些看似无害的改变,要么仅仅是因为你不在身边保护它,因为它静静地运行amok 并删除所有客户的数据。

那么你应该怎么做呢?

一件事是尽可能使用现代编译器,并打开其警告并注意它们。 (好的编译器甚至有一个叫做“将警告视为错误”的选项,关心正确程序的程序员通常会打开这个选项。)尽管正如我所说,他们不需要这样做,但编译器越来越擅长如果您要求他们检测未定义的行为并警告您。

然后另一件事,如果您要进行大量 C 编程,则要注意学习这门语言,您可以做什么,不应该做什么。 一定要编写适合正确原因的程序 不要满足于今天似乎有效的程序。 如果有人指出你依赖于未定义的行为,不要说,“但我的程序有效——我为什么要关心?” (你没这么说,但有些人是这么说的。)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM