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

时间:2016-05-07 10:19:53

标签: c

我是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;
}

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

1 个答案:

答案 0 :(得分:8)

你说你的第一个节目&#34;工作&#34;,但它只是偶然起作用。它就像一辆汽车在路上行驶,前轮上没有固定螺丝,只有一些奇迹他们还没有掉下来 - 但是。

你说

char *lineName;

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

接下来你说

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

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

未初始化指针的情况考虑起来有点棘手,因为一如既往,使用指针我们必须区分指针的值而不是内存中的数据指针指向。之前我提到说int i并询问i的价值是多少。现在,i中的某些位模式 - 它可能是0,或1,或-23,或8675309。

同样,在lineName中会有一些位模式 - 它可能会指向&#34;内存位置0x00000000,或0xffe01234,或0xdeadbeef。但问题是:那个位置是否有任何内存,我们是否有权写入它,是否还用于其他任何事情? 如果有内存并且我们有权限并且它没有被用于其他任何内容,那么该程序可能似乎正常工作 - 现在。但那些是三个相当大的ifs!如果内存不存在,或者我们没有权限写入内存,则程序尝试时可能会崩溃。如果内存被用于其他内容,当我们要求scanf在那里写一个字符串时会出现问题。

而且,实际上,如果我们关心的是编写有效的程序(并且工作原因正确),我们不必提出任何这些问题。我们不必询问当我们不对其进行初始化时lineName点,或者是否存在任何内存,或者我们是否有权写入它,或者它是否&# #39;被用于别的东西。相反,我们应该只是初始化 lineName!我们应该明确指出我们拥有的内存以及我们 允许写入并且 isn&#39; t 正在使用除了别的什么!

有几种方法可以做到这一点。最简单的方法是使用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个或更多字符怎么办?这将导致scanflineName写入20个或更多字符,超过lineName内存允许持有的结束时间,并且我们会返回在写入记忆的情况下,我们不会拥有并且可能正在使用其他东西。

一种解决方案是让lineName更大 - 将其声明为char lineName[100],或致电malloc(100)。但这只会解决问题 - 现在我们不得不担心用户输入100个或更多字符的可能性(可能更小)。接下来要做的就是告诉scanf不要向lineName写更多内容,而不是我们安排它保留。这实际上非常简单:如果lineName仍设置为包含20个字符,请调用

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

格式说明符%19s告诉scanf它只允许读取并存储长达19个字符的字符串。

现在,我已经在这里说了很多,但我意识到我实际上还没有回答过为什么你的程序从工作变为崩溃的问题,当你做出看似微不足道的,看似无关的变化时。这最终成为一个难以回答的难题。回到我开始的类比,它就像问你为什么能够毫无问题地将没有胡桃的车开到商店,但是当你试图开车到奶奶家时,轮子摔倒了,你撞到了一条沟里。有一百万种可能的因素可能会发挥作用,但它们都没有改变这样一个根本的事实:驾驶一辆没有固定轮子的汽车是一个疯狂的想法,而且根本无法保证工作。

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

无论如何,对于程序的第一个版本,lineName最终包含一个随机值,该值对应于指向实际内存的指针,您可以通过写入来逃避。但是当你添加第四个变量b时,事情就会移动到足以使lineName成为指向不存在的内存的指针,或者你没有写入权限来了,你的程序崩溃了。

有意义吗?

现在,还有一件事,如果你还在和我在一起。如果你停下来想想,整件事情可能会让人感到不安。你有一个程序(你的第一个程序)似乎工作得很好,但实际上有一个相当可怕的错误。它写入随机,未分配的内存。但是当你编译它时,你没有致命的错误消息,当你运行它时没有任何迹象表明有任何不妥。那是什么?

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

事实证明,有三种C程序,我们称之为好,坏,丑。

  • 良好的计划工作正确。他们不违反任何规则,他们不做任何非法行为。在编译它们时,它们不会收到任何警告或错误消息,当您运行它们时,它们就可以正常工作。

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

  • 但是有些丑陋的程序会参与未定义的行为。这些是打破不同规则的那些规则,由于各种原因,编译器有义务抱怨。 (实际上,编译器甚至可能无法检测到它们)。参与未定义行为的程序可以任何

让我们再考虑最后两点。编写使用未定义行为的程序时,编译器没有义务生成错误消息,因此您可能没有意识到您已经完成了它。并允许程序执行任何操作,包括按预期工作。但是,既然它被允许做任何事情,它可能会在明天停止工作,似乎完全没有理由,或者是因为你做了一些看似无害的改变,或者仅仅是因为你不能防守它,因为它悄悄地运行并删除所有客户的数据。

那么你应该怎么做呢?

如果可以,有一点是使用现代编译器,并打开警告,并注意它们。 (好的编译器甚至有一个名为&#34的选项;将警告视为错误&#34;并且关心正确程序的程序员通常会打开此选项。)尽管如我所说,他们并不需要,如果你要求,编译器会越来越好地检测未定义的行为并向你发出警告。

然后另一件事,如果你要做很多C编程,就要注意学习语言,你允许做什么,你做什么不应该这样做。重点编写出于正确理由工作的程序。不要满足于似乎只是工作的计划。如果有人指出你依赖于未定义的行为,不要说,&#34;但我的计划有效 - 我为什么要关心?&#34; (你没有这么说,但有些人这样做。)