简体   繁体   English

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

[英]C Program crashes when adding an extra int

I am new to C and using Eclipse IDE The following code works fine:我是 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;
}

Input:输入:

Green (21)

Red (38)

Output:输出:

Green (21)

Red (38)
0123401234

However, when simply add a new int:但是,当简单地添加一个新的 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;
}

The program will crash with the same input.程序将因相同的输入而崩溃。 Can anybody tell me why?谁能告诉我为什么?

You said your first program "works", but it works only by accident.你说你的第一个程序“有效”,但它只是偶然的。 It's like a car zooming down the road with no lugnuts holding on the front wheels, only by some miracle they haven't fallen off -- yet.这就像一辆汽车在前轮上没有拉着螺母的情况下在路上快速行驶,只是奇迹般地它们还没有掉下来 - 还没有。

You said你说

char *lineName;

This gives you a pointer variable that can point to some characters, but it doesn't point anywhere yet .这为您提供了一个可以指向某些字符的指针变量,但它尚未指向任何地方 The value of this pointer is undefined.此指针的值未定义。 It's sort of like saying " int i " and asking what the value of i is.这有点像说“ int i ”并询问i的值是什么。

Next you said接下来你说

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

You're asking scanf to read a line name and store the string in the memory pointed to by lineName .您要求scanf读取行名称并将字符串存储在lineName指向的内存中。 But where is that memory?但是那个记忆在哪里? We have no idea whatsoever!我们什么都不知道!

The situation with uninitialized pointers is a little trickier to think about because, as always, with pointers we have to distinguish between the value of the pointer as opposed to the data at the memory which the pointer points to .考虑未初始化指针的情况有点棘手,因为与往常一样,对于指针,我们必须区分指针的值与指针指向的内存中的数据 Earlier I mentioned saying int i and asking what the value of i is.前面我提到说int i和询问的价值是什么i是。 Now, there's going to be some bit pattern in i -- it might be 0, or 1, or -23, or 8675309.现在, i会有一些位模式——它可能是 0、1、-23 或 8675309。

Similarly, there's going to be some bit pattern in lineName -- it might "point at" memory location 0x00000000, or 0xffe01234, or 0xdeadbeef.同样, lineNamelineName有一些位模式——它可能“指向”内存位置 0x00000000、0xffe01234 或 0xdeadbeef。 But then the questions are: is there actually any memory at that location, and do we have permission to write to it, and is it being used for anything else?但接下来的问题是:那个位置实际上是否有任何内存,我们是否有权写入它,它是否被用于其他用途? If there is memory and we do have permission and it's not being used for anything else, the program might seem to work -- for now.如果有内存并且我们确实有权限并且它没有被用于其他任何事情,那么该程序似乎可以工作——目前。 But those are three pretty big ifs!但那是三个相当大的ifs! If the memory doesn't exist, or if we don't have permission to write to it, the program is probably going to crash when it tries.如果内存不存在,或者如果我们没有写入权限,程序可能会在尝试时崩溃。 And if the memory is being used for something else, something's going to go wrong -- if not now, then later -- when we ask scanf to write its string there.如果内存被用于其他用途,那么当我们要求scanf在那里写入它的字符串时,就会出现问题——如果不是现在,那么以后。

And, really, if what we care about is writing programs that work (and that work for the right reasons), we don't have to ask any of these questions.而且,真的,如果我们关心的是编写有效的程序(并且出于正确的原因而有效),我们就不必问任何这些问题。 We don't have to ask where lineName points when we don't initialize it, or whether there's any memory there, or if we have permission to write to it, or if it's being used for something else.当我们不初始化lineName时,我们不必询问lineName指向哪里,或者那里是否有任何内存,或者我们是否有权写入它,或者它是否被用于其他用途。 Instead, we should simply, actually, initialize lineName !相反,我们应该简单地,实际上,初始化lineName We should explicitly make it point to memory that we do own and that we are allowed to write to and that isn't being used for anything else!我们应该明确它指向的内存,我们自己的,我们允许写和被用于其他任何东西!

There are several ways to do this.有几种方法可以做到这一点。 The easiest is to use an array for lineName , not a pointer:最简单的方法是对lineName使用数组,而不是指针:

char lineName[20];

Or, if we have our hearts set on using a pointer, we can call malloc :或者,如果我们决定使用指针,我们可以调用malloc

char *lineName = malloc(20);

However, if we do that, we have to check to make sure malloc succeeded:但是,如果我们这样做,我们必须检查以确保malloc成功:

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

If you make either of those changes, your program will work.如果您进行其中任何一项更改,您的程序就会运行。

...Well, actually, we're still in a situation where your program will seem to work, even though it still has another, pretty serious, lurking problem. ...嗯,实际上,我们仍然处于您的程序似乎可以工作的情况,即使它还有另一个非常严重的潜在问题。 We've allocated 20 characters for lineName , which gives us 19 actual characters, plus the trailing '\\0' .我们为lineName分配了 20 个字符,这给了我们 19 个实际字符,加上尾随'\\0' But we don't know what the user is going to type.但是我们不知道用户要输入什么。 What if the user types 20 or more characters?如果用户键入 20 个或更多字符怎么办? That will cause scanf to write more than 20 characters to lineName , off past the end of what lineName 's memory is allowed to hold, and we're back in the situation of writing to memory that we don't own and that might be in use for something else.这将导致scanf将超过 20 个字符写入lineName ,超过lineName的内存允许保留的末尾,我们又回到了写入我们不拥有的内存的情况,这可能是用于其他用途。

One solution is to make lineName bigger -- declare it as char lineName[100] , or call malloc(100) .一种解决方案是使lineName更大——将其声明为char lineName[100] ,或调用malloc(100) But that just moves the problem around -- now we have to worry about the (perhaps smaller) chance that the user will type 100 or more characters.但这只是解决了问题——现在我们不得不担心(可能更小)用户输入 100 个或更多字符的可能性。 So the next thing to do is to tell scanf not to write more to lineName than we've arranged for it to hold.所以接下来要做的是告诉scanf不要向lineName写入比我们安排的更多的内容。 This is actually pretty simple.这实际上很简单。 If lineName is still set up to hold 20 characters, just call如果lineName仍然设置为容纳 20 个字符,只需调用

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

That format specifier %19s tells scanf that it's only allowed to read and store a string of up to 19 characters long, leaving one byte free for the terminating '\\0' that it's also going to add.格式说明符%19s告诉scanf它只允许读取和存储最多 19 个字符长的字符串,留出一个字节用于终止'\\0'它还将添加。


Now, I've said a lot here, but I realize I haven't actually gotten around to answering the question of why your program went from working to crashing when you made that seemingly trivial, seemingly unrelated change.现在,我在这里说了很多,但我意识到我实际上还没有来得及回答这个问题,当你做出看似微不足道的、看似无关的改变时,你的程序为什么会从工作变成崩溃。 This ends up being a hard question to answer satisfactorily.这最终成为一个很难令人满意地回答的问题。 Going back to the analogy I started this answer with, it's like asking why you were able to drive the car with no lugnuts to the store with no problem, but when you tried to drive to grandma's house, the wheels fell off and you crashed into a ditch.回到我开始回答这个问题时的比喻,这就像问为什么你能把没有 lugnuts 的车开到商店没有问题,但是当你试图开车去奶奶家时,车轮掉了,你撞上了一条沟。 There are a million possible factors that might have come into play, but none of them change the underlying fact that driving a car with the wheels not fastened on is a crazy idea, that's not guaranteed to work at all.有一百万种可能的因素可能会起作用,但它们都没有改变这样一个基本事实,即在车轮未系紧的情况下驾驶汽车是一个疯狂的想法,这根本不能保证有效。

In your case, the variables you're talking about -- lineName , stationNo , i , and then b -- are all local variables, typically allocated on the stack.在您的情况下,您正在谈论的变量 -- lineNamestationNoib -- 都是局部变量,通常分配在堆栈上。 Now, one of the characteristics of the stack is that it gets used for all sorts of stuff, and it never gets cleared between uses.现在,堆栈的特征之一是它被用于各种东西,并且在使用之间永远不会被清除。 So if you have an uninitialized local variable, the particular random bits that it ends up containing depend on whatever was using that piece of the stack last time.因此,如果您有一个未初始化的局部变量,它最终包含的特定随机位取决于上次使用该堆栈的内容。 If you change your program slightly so that different functions get called, those different functions may leave different random values lying around on the stack.如果您稍微更改程序以调用不同的函数,这些不同的函数可能会在堆栈上留下不同的随机值。 Or if you change your function to allocate different local variables, the compiler may place them in different spots on the stack, meaning that they'll end up picking up different random values from whatever was there last time.或者,如果您更改函数以分配不同的局部变量,编译器可能会将它们放在堆栈上的不同位置,这意味着它们最终会从上次存在的任何值中获取不同的随机值。

Anyway, somehow, with the first version of your program, lineName ended up containing a random value that corresponded to a pointer that pointed to actual memory which you could get away with writing to.无论如何,不​​知何故,在您的程序的第一个版本中, lineName最终包含一个随机值,该值对应于指向实际内存的指针,您可以通过写入而逃脱。 But when you added that fourth variable b , things moved around just enough that lineName ended up pointing to memory that didn't exist or that you didn't have permission to write to, and your program crashed.但是,当您添加第四个变量b ,事情发生了足够的变化, lineName最终指向了不存在或您无权写入的内存,并且您的程序崩溃了。

Make sense?有道理吗?


And now, one more thing, if you're still with me.现在,还有一件事,如果你还在我身边。 If you stop and think, this whole thing might be kind of unsettling.如果你停下来想一想,整个事情可能会令人不安。 You had a program (your first program) that seemed to work just fine, but actually had a decently horrible bug.你有一个程序(你的第一个程序)看起来运行得很好,但实际上有一个相当可怕的错误。 It wrote to random, unallocated memory.它写入随机的、未分配的内存。 But when you compiled it you got no fatal error messages, and when you ran it there was no indication that anything was amiss.但是当你编译它时,你没有得到致命的错误信息,当你运行它时,没有任何迹象表明有任何问题。 What's up with that?那是怎么回事?

The answer, as a couple of the comments alluded to, involves what we call undefined behavior .正如一些评论所提到的,答案涉及我们所说的未定义行为

It turns out that there are three kinds of C programs, which we might call the good, the bad, and the ugly.事实证明,存在三种 C 程序,我们可以称之为好的、坏的和丑陋的。

  • Good programs work for the right reasons.好的程序有正确的理由。 They don't break any rules, they don't do anything illegal.他们不违反任何规则,他们不做任何违法的事情。 They don't get any warnings or error messages when you compile them, and when you run them, they just work.当您编译它们时,它们不会收到任何警告或错误消息,而当您运行它们时,它们就可以正常工作。

  • Bad programs break some rule, and the compiler catches this, and issues a fatal error message, and declines to produce a broken program for you to try to run.坏程序破坏了某些规则,编译器会捕捉到这一点,并发出致命错误消息,并拒绝生成破坏程序供您尝试运行。

  • But then there are the ugly programs, that engage in undefined behavior .但是还有一些丑陋的程序,它们从事未定义的行为 These are the ones that break a different set of rules, the ones that, for various reasons, the compiler is not obliged to complain about.这些是违反一组不同规则的规则,由于各种原因,编译器没有义务抱怨这些规则。 (Indeed the compiler may or may not even be able to detect them). (实际上,编译器可能会也可能不会检测到它们)。 And programs that engage in undefined behavior can do anything .从事未定义行为的程序可以做任何事情

Let's think about that last point a little more.让我们再考虑一下最后一点。 The compiler is not obligated to generate error messages when you write a program that uses undefined behavior, so you might not realize you've done it.当您编写使用未定义行为的程序时,编译器没有义务生成错误消息,因此您可能没有意识到自己已经这样做了。 And the program is allowed to do anything, including work as you expect.并且该程序可以做任何事情,包括您期望的工作。 But then, since it's allowed to do anything, it might stop working tomorrow, seemingly for no reason at all, either because you made some seemingly innocuous change to it, or merely because you're not around to defend it, as it quietly runs amok and deletes all your customer's data.但是,既然它被允许做任何事情,它可能会在明天停止工作,似乎没有任何理由,要么是因为你对它做了一些看似无害的改变,要么仅仅是因为你不在身边保护它,因为它静静地运行amok 并删除所有客户的数据。

So what are you supposed to do about this?那么你应该怎么做呢?

One thing is to use a modern compiler if you can, and turn on its warnings, and pay attention to them.一件事是尽可能使用现代编译器,并打开其警告并注意它们。 (Good compilers even have an option called "treat warnings as errors", and programmers who care about correct programs usually turn this option on.) Even though, as I said, they're not required to, compilers are getting better and better at detecting undefined behavior and warning you about it, if you ask them to. (好的编译器甚至有一个叫做“将警告视为错误”的选项,关心正确程序的程序员通常会打开这个选项。)尽管正如我所说,他们不需要这样做,但编译器越来越擅长如果您要求他们检测未定义的行为并警告您。

And then the other thing, if you're going to be doing a lot of C programming, is to take care to learn the language, what you're allowed to do, what you're not supposed to do.然后另一件事,如果您要进行大量 C 编程,则要注意学习这门语言,您可以做什么,不应该做什么。 Make a point of writing programs that work for the right reasons .一定要编写适合正确原因的程序 Don't settle for a program that merely seems to work today.不要满足于今天似乎有效的程序。 And if someone points out that you're depending on undefined behavior, don't say, "But my program works -- why should I care?"如果有人指出你依赖于未定义的行为,不要说,“但我的程序有效——我为什么要关心?” (You didn't say this, but some people do.) (你没这么说,但有些人是这么说的。)

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

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