简体   繁体   English

为什么这段代码适用于 gcc 6.3.0 而不是 4.9.2?

[英]Why does this code work on gcc 6.3.0 and not 4.9.2?

I have a game for a class that made to show binary search.我有一个用于显示二分搜索的类的游戏。 The code compiles and runs with no errors on my ide(atom) and the gcc compiler (gcc version 6.3.0 (MinGW.org GCC-6.3.0-1)).代码在我的 ide(atom) 和 gcc 编译器(gcc 版本 6.3.0 (MinGW.org GCC-6.3.0-1))上编译和运行没有错误。 My professor is using Dev C++, and his compiler is (gcc 4.9.2).我的教授使用的是 Dev C++,他的编译器是 (gcc 4.9.2)。 Unfortunately, his version will compile without an error but also fails when intaking the user's input from Normal Game.不幸的是,他的版本编译时不会出错,但在从普通游戏中获取用户输入时也会失败。 I am not skilled enough at debugging to figure out what is wrong.我在调试方面不够熟练,无法找出问题所在。 Any help is appreciated, and explanations would be incredible.任何帮助表示赞赏,解释将是令人难以置信的。

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



//--Menu--//
//user input for main menu
int userInput(){
  int var;
  scanf("%d", &var);
  return var;
}
//main menu for the user to decide what version they want to play
void menuMain(){
  printf("------------------------\n");
  printf("Menu\n");
  printf("1 - Normal Game\n");
  printf("2 - Efficency Mode\n");
  printf("3 - Quick Game\n");
  printf("4 - EXIT PROGRAM\n");
  printf("------------------------\n");
};
//good bye message
void menuGoodBye(){
  printf("---------------\n");
  printf("Good Bye!\n");
  printf("---------------\n");
}
//gets the users response and capitalizes it
char responseUser(int guess){
  char y;
  printf("---------------------------------------------\n");
  printf("Is it %d?\n",guess);
  printf("Please respond with Lower (L), Higher (H), or Correct (C).\n");
  scanf("%s",&y);
  y = toupper(y);
  return y;
}

//--Normal Game--//
//instructions for the user Normal Games
void instructions(int min,int max){
  printf("----------------------------------------------------------------------\n");
  printf("Pick a number from %d to %d and I will try and guess it.\n",min, 
  max);
  printf("If your number is lower, respond with a L.\n");
  printf("If your number is higher, respond with a H.\n");
  printf("If I guess correctly, respond with a C.\n");
  printf("----------------------------------------------------------------------\n");
}
//uses binary search for efficient gameplay
int efficentGuesser(int min, int max){
  return (max+min)/2;
}
//uses random numbers with updated minimums and maximums
int gameGuesser(int min, int max){
  int x;
  x=rand()%(max+1-min)+min;
  return x;
}
//the modular switch for the game
void gameSwitch(int (*method)(int, int),int min, int max){
  char response;
  int guess;
  do{
     guess = (*method)(min,max);
     response = responseUser(guess);
     switch (response) {
       case 'L':
         max= guess-1;
       break;
       case 'H':
         min= guess+1;
       break;
       case 'C':
       break;
       default:
       break;
     }
   }while(response != 'C');
}


//--Quick Game--//
//instructions for the quick game
void instructionsQuickGame(int min, int max){
  printf("----------------------------------------------------------------------\n");
  printf("Pick a number from %d to %d and I will try and guess it.\n",min, 
  max);
  printf("----------------------------------------------------------------------\n");
}
//search for a quick game
void quickGame(int (*method)(int, int),int x, int y){
  int input,guess,min,max, trys;
    input= userInput();

    clock_t begin = clock();
    min = x;
    max = y;

  do{
    guess=(*method)(min, max);
    if(input > guess){
      min = guess + 1;
    }else{
      max = guess - 1;
    }
    trys++;
  }while(input != guess);
  clock_t end = clock();

  printf("Wow,That took me %.8f seconds and %d trys!\n",(double)(end - 
  begin) / CLOCKS_PER_SEC,trys);

}

//--Modular Block--//
//basic building block for game, you can import functions modularly
void defaultGame(int (*method)(int, int), void (*s)(), void(*instructions) 
(int, int)){
  int min, max;
  min =0;
  max =100;
  (*instructions)(min,max);
  (*s)((*method),min,max);

}


//the actual code that runs
int main(){
  srand(time(0));
  int response;
  do{
    menuMain();
    response=userInput();
    switch (response){
      case 1:
        //defaultGame(method, what switch, what instructions)
        defaultGame(gameGuesser, gameSwitch, instructions);
      break;
      case 2:
        //defaultGame(method, what switch, what instructions)
        defaultGame(efficentGuesser, gameSwitch,instructions);
      break;
      case 3:
        //defaultGame(method, what switch, what instructions)
        defaultGame(efficentGuesser, quickGame, instructionsQuickGame);
      break;
      case 4:
        menuGoodBye();
      break;
      default:
        printf("Please Pick a number from the menu.\n");
      break;
    }
  }while(response != 4);

  return EXIT_SUCCESS;
}

Undefined Behavior (UB)未定义行为 (UB)

char y; ... scanf("%s",&y); is undefined behavior as "%s" directs scanf() to form a string of at least one non-white-space character plus an appended null character .是未定义的行为"%s"指示scanf() ,以形成至少一个非空格字符的字符串加上一个附加的空字符 char y is too small for that. char y太小了。 @Eugene Sh. @尤金·什。

Consider the following instead.请考虑以下内容。

scanf(" %c",&y);

Other problems exists too, yet the above is a big one.其他问题也存在,但以上是一个大问题。

The userInput() and responseUser() functions are defective; userInput()responseUser()函数有缺陷; you need to fix them.你需要修复它们。

The fatal error in responseUser() is (as chux already mentioned in another answer) is to use the %s scan conversion specifier to a single character, char y . responseUser()的致命错误是(正如chux在另一个答案中已经提到的那样)是将%s扫描转换说明符用于单个字符char y

Both functions ignore any and all issues with the input.这两个函数都忽略输入的任何和所有问题。 They do not check if the conversion was successful (the return value from scanf() ), and simply assume it was.他们不检查转换是否成功(来自scanf()的返回值),而只是假设它是成功的。


Since standard input is line-buffered, it is best to read the input line by line.由于标准输入是行缓冲的,因此最好逐行读取输入。 (That is, when user types something, it is only passed to the program when they press Enter.) There are at least three ways to do this ( fgets() , an fgetc() loop, and scanf() ), but since OP is using scanf() , let's see how it is best used. (也就是说,当用户键入某些内容时,它只会在他们按下 Enter 键时才传递给程序。)至少有三种方法可以做到这一点( fgets() 、 fgetc() 循环和scanf() ),但是由于OP 正在使用scanf() ,让我们看看如何最好地使用它。

The "trick" is using conversion pattern " %1[^\\n]%*[^\\n]" . “技巧”是使用转换模式" %1[^\\n]%*[^\\n]" This actually consists of three parts.这实际上由三部分组成。 The leading space tells scanf() to skip all whitespace, including newlines.前导空格告诉scanf()跳过所有空格,包括换行符。 The %1[^\\n] is a proper conversion, an one-character string that is not a newline ( \\n ). %1[^\\n]是一个适当的转换,一个不是换行符 ( \\n ) 的单字符字符串。 The target is an array of at least two characters, since one is taken by the character itself, and the other is needed for the end-of-string nul character ( \\0 ).目标是一个至少包含两个字符的数组,因为一个由字符本身获取,另一个用于字符串结尾的 nul 字符 ( \\0 )。 The %*[^\\n] is a skipped conversion, meaning it does not actually convert anything, but will skip any non-newline characters. %*[^\\n]是一个跳过的转换,这意味着它实际上不会转换任何东西,但会跳过任何非换行符。 Because it is skipped, and no literals or non-skipped conversions follow in the pattern, it is also optional.因为它被跳过,并且模式中没有文字或非跳过转换,所以它也是可选的。

The idea of that conversion pattern is that the space in front consumes all whitespace, including any newlines or empty lines left in the input buffer.这种转换模式的想法是前面的空间消耗所有空白,包括输入缓冲区中留下的任何换行符或空行。 Then, we convert an one-character string, and skip everything else on that line (but not the newline at the end of the line, which is consumed by the next one as part of skipping whitespace).然后,我们转换一个单字符的字符串,并跳过该行上的所有其他内容(但不包括行尾的换行符,它作为跳过空格的一部分被下一行使用)。

Here is an example function that returns the first character, converted to uppercase, of the input line;这是一个示例函数,它返回输入行的第一个字符,转换为大写; or EOF if an error occurs (for example, user presses Ctrl + D at the beginning of a line to end standard input):或 EOF 如果发生错误(例如,用户在行首按Ctrl + D以结束标准输入):

int  input(void)
{
    char  first[2];

    if (scanf(" %1[^\n]%*[^\n]", first) == 1)
        return toupper( (unsigned char)(first[0]) );

    return EOF;
}

Note that toupper() takes a character code cast to unsigned char .请注意, toupper()将字符代码转换unsigned char (The reason for that boils down to the fact that the C standard does not say whether char is a signed type or not, but the <ctype.h> functions, including isalpha() and such, are defined as taking an unsigned char character code.) (其原因归结为 C 标准没有说明char是否是有符号类型的事实,但是<ctype.h>函数,包括isalpha()等,被定义为采用无符号 char 字符代码。)

Consider the following small main game menu program as an example:以下面的小游戏主菜单程序为例:

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

int  input(void)
{
    char  first[2];
    if (scanf(" %1[^\n]%*[^\n]", first) == 1)
        return toupper( (unsigned char)(first[0]) );
    else
        return EOF;
}

void normal_game(void) { printf("<normal game played>\n"); }
void efficiency_game(void) { printf("<efficiency game played>\n"); }
void quick_game(void) { printf("<quick game played>\n"); }

int main(void)
{

    if (!setlocale(LC_ALL, ""))
        fprintf(stderr, "Warning: Your C library does not support your current locale.\n");

    while (1) {
        printf("------------------------\n");
        printf("Menu\n");
        printf("1 - Normal Game\n");
        printf("2 - Efficency Mode\n");
        printf("3 - Quick Game\n");
        printf("4 - EXIT PROGRAM\n");
        printf("------------------------\n");

        switch (input()) {

        case '1':
            normal_game();
            break;

        case '2':
            efficiency_game();
            break;

        case '3':
            quick_game();
            break;

        case '4':
            exit(EXIT_SUCCESS);

        case EOF:
            printf("No more input; exiting.\n");
            exit(EXIT_SUCCESS);

        default:
            printf("That is not a valid choice!\n");
        }
    }
}

If you compile (enabling warnings; I use -Wall -O2 with all versions of GCC) and run the above, you can experiment with how it responds if you type incorrect choices, or press Ctrl + D at the beginning of a line.如果您编译(启用警告;我在所有版本的 GCC 中使用-Wall -O2 )并运行上面的代码,您可以尝试在输入不正确的选项时它的响应方式,或者在行的开头按Ctrl + D。

Note that this does limit the input to a single character.请注意,这确实将输入限制为单个字符。


The responseUser() function is now simple to implement: responseUser()函数现在很容易实现:

int  responseUser(int guess)
{
    printf("---------------------------------------------\n");
    printf("Is it %d (C), higher (H), or lower (L)?\n", guess);

    while(1) {
        switch (input()) {
        case 'C': return  0;
        case 'H': return +1;
        case 'L': return -1;
        case EOF:
            printf("No more input, so aborting.\n");
            exit(EXIT_SUCCESS);
        default:
            printf("Please input C, H, or L.\n");
        }
    }
}

Note that we could also use case 'C': case 'c': ... above, to accept both lower and upper case letters, and drop the toupper() from the input() function.请注意,我们还可以使用case 'C': case 'c': ...以上,接受小写和大写字母,并从input()函数中删除toupper()


Let's say you extend the game by allowing the user to first set the range of integers.假设您通过允许用户首先设置整数范围来扩展游戏。 So, we'll want to read numbers as input.所以,我们要读取数字作为输入。 The problem with scanf() in this case is that if the user types something other than a number, it is left in the buffer.在这种情况下scanf()的问题是,如果用户键入的不是数字,它会留在缓冲区中。 Repeating the scanf() call will just retry converting the same input in the buffer, and will fail.重复scanf()调用只会重试转换缓冲区中的相同输入,并且会失败。 (We can clear the input buffer by consuming input until a newline or an EOF is encountered, but that tends to be fragile, leading to behaviour where the program seems to "ignore" input lines, really being one line behind in processing input.) (我们可以通过消耗输入直到遇到换行符或 EOF 来清除输入缓冲区,但这往往是脆弱的,导致程序似乎“忽略”输入行的行为,实际上是在处理输入时落后一行。)

To avoid that, it is best to use the fgets() approach instead.为避免这种情况,最好改用fgets()方法。 Fortunately, that approach allows us to accept the input in more forms than one.幸运的是,这种方法允许我们以多种形式接受输入。 For example:例如:

#define  MAX_LINE_LEN  200

int choose_range(int *min_to, int *max_to)
{
    char  buffer[MAX_LINE_LEN], *line;
    int   min, max;
    char  dummy;

    printf("What is the range of integers?\n");

    while (1) {
        /* Read next input line. */
        line = fgets(buffer, sizeof buffer, stdin);
        if (!line)
            return -1; /* No more input; no range specified. */

        if (sscanf(line, " %d %d %c", &min, &max, &dummy) == 2 ||
            sscanf(line, " %d %*s %d %c", &min, &max, &dummy) == 2 ||
            sscanf(line, " %*s %d %*s %d %c", &min, &max, &dummy) == 2) {
            if (min <= max) {
                *min_to = min;
                *max_to = max;
            } else {
                *min_to = max;
                *max_to = min;
            }
            return 0; /* A range was specified! */
        }

        printf("Sorry, I don't understand that.\n");
        printf("Please state the range as minimum to maximum.\n");
    }
}

Here, we use buffer array as a buffer to buffer a whole input line.在这里,我们使用buffer数组作为缓冲区来缓冲整个输入行。 The fgets() function returns a pointer to it, if there is more input.如果有更多输入, fgets()函数会返回一个指向它的指针。 (If there is no more input, or a read error occurs, it returns NULL.) (如果没有更多输入,或发生读取错误,则返回 NULL。)

If we do read a line, we try the conversion pattern " %d %d %c" first.如果我们确实阅读了一行,我们首先尝试转换模式" %d %d %c" The %c converts a single character, and is used as a sentinel test: If the entire pattern converts, the result is 3, but there was at least one extra non-whitespace character following the second integer. %c转换单个字符,并用作哨兵测试:如果整个模式转换,结果为 3,但在第二个整数之后至少有一个额外的非空白字符。 If sscanf() returns two, it means there was just the two integers (possibly followed by whitespace, say a newline), and that's what we want.如果sscanf()返回两个,则意味着只有两个整数(可能后跟空格,比如换行符),这就是我们想要的。

If that pattern didn't work, we try " %d %*s %d %c" next.如果该模式不起作用,我们接下来尝试" %d %*s %d %c" This is similar, except now there is a skipped conversion, %*s , between the two integers.这是类似的,除了现在两个整数之间有一个跳过的转换%*s It can be any sequence of non-whitespace characters, for example to or .. .它可以是任何非空白字符序列,例如to.. The idea is that this will match input like 5 to 15 and 1 .. 100 .这个想法是,这将匹配5 to 151 .. 100

If that did not work either, we try " %*s %d %*s %d %c" .如果这也不起作用,我们尝试" %*s %d %*s %d %c" I'm sure you already know what it is for: to match input like from 5 to 15 or between 1 and 100 , but ignoring the words, only converting the integers.我相信您已经知道它的用途:匹配from 5 to 15between 1 and 100输入,但忽略单词,只转换整数。

The function itself returns 0 if a range was specified, and nonzero otherwise.如果指定了范围,则函数本身返回 0,否则返回非零。 You can use it like this:你可以这样使用它:

    int  minimum_guess, maximum_guess;

    if (choose_range(&minimum_guess, &maximum_guess)) {
        printf("Oh okay, no need to be rude! Bye!\n");
        exit(EXIT_FAILURE);
    } else
    if (minimum_guess == maximum_guess) {
        printf("I'm not *that* stupid, you know. Bye!\n");
        exit(EXIT_FAILURE);
    }

    printf("Okay, I shall guess what number you are thinking of,\n");
    printf("between %d and %d, including those numbers.\n",
           minimum_guess, maximum_guess);

There is also a potential problem with how OP is trying to time a game. OP 如何为游戏计时也存在潜在问题。

The clock() function returns the amount of CPU time elapsed, in units of CLOCKS_PER_SEC per second. clock()函数以每秒CLOCKS_PER_SEC为单位返回经过的 CPU 时间量。 (That is, (double)(stopped - started) / (double)CLOCKS_PER_SEC returns the number of seconds of CPU time between started = clock() and stopped = clock() .) (也就是说, (double)(stopped - started) / (double)CLOCKS_PER_SEC返回started = clock()stopped = clock()之间的 CPU 时间的秒数。)

The problem is that it is CPU time, not real world time.问题是它是 CPU 时间,而不是现实世界时间。 It is only the duration during which the program did actual computation, and does not include waiting for user input.它只是程序进行实际计算的持续时间,不包括等待用户输入的时间。

Furthermore, it can have a rather low resolution.此外,它可能具有相当低的分辨率。 (On most systems, CLOCKS_PER_SEC is one million, but the value clock() returns increments in large steps. That is, if you call it repeatedly in a loop and print the values, on some systems it prints the same value a lot of times, then jumps to a much bigger value and stays there for a long time, and so on; often the precision is just a hundredth of a second or so.) (在大多数系统上, CLOCKS_PER_SEC是一百万,但值clock()以大步长返回增量。也就是说,如果您在循环中重复调用它并打印值,在某些系统上它会多次打印相同的值,然后跳转到一个更大的值并在那里停留很长时间,依此类推;通常精度只有百分之一秒左右。)

If we wanted to measure wall clock time at high precision, we can use clock_gettime(CLOCK_REALTIME) on most systems, and some Windows kludge on Windows:如果我们想以高精度测量挂钟时间,我们可以在大多数系统上使用clock_gettime(CLOCK_REALTIME)Windows上使用一些Windows clock_gettime(CLOCK_REALTIME)

#define _POSIX_C_SOURCE  200809L
#include <time.h>

static struct timespec  mark;

static inline void wallclock_mark(void)
{
    clock_gettime(CLOCK_REALTIME, &mark);
}

static inline double wallclock_elapsed(void)
{
    struct timespec  now;
    clock_gettime(CLOCK_REALTIME, &now);
    return (double)(now.tv_sec - mark.tv_sec)
         + (double)(now.tv_nsec - mark.tv_nsec) / 1000000000.0;
}

(If you disagree with "kludge", just look at the code needed.) (如果您不同意“kludge”,请查看所需的代码。)

With the above code (including the additional code you need from that other answer linked to, if you use Windows and your C library does not support POSIX.1clock_gettime() ), you can measure wall clock taken using eg使用上面的代码(包括您需要从链接到的其他答案中的附加代码,如果您使用 Windows 并且您的 C 库不支持 POSIX.1clock_gettime() ),您可以使用例如测量挂钟

double  seconds;

wallclock_mark();
/* Do something */
seconds = wallclock_elapsed();

With that, we can easily measure the wall clock duration of each game.有了这个,我们可以轻松地测量每场比赛的挂钟持续时间。

However, if we wanted to split that into how much time is taken for computation, and how much time was taken to wait/process user input, it becomes problematic, because those things can happen at the same time in real life .但是,如果我们想将其拆分为计算所花费的时间,以及等待/处理用户输入所花费的时间,就会出现问题,因为这些事情在现实生活中可能同时发生 If we wanted to do that, we'd better switch to eg Ncurses, so we can receive each keypress when they occur.如果我们想这样做,我们最好切换到例如 Ncurses,这样我们就可以在它们发生时接收每个按键。

In short, the clock() use is not necessarily wrong;总之, clock()使用不一定是错误的; it is just an issue of what one is trying to measure, and conveying that correctly to the user as well.这只是人们试图测量什么并将其正确传达给用户的问题。

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

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