为什么此代码在gcc 6.3.0而非4.9.2上有效?

时间:2019-01-11 16:48:48

标签: c gcc

我有一个游戏让一个类显示二进制搜索。该代码可以在我的ide(atom)和gcc编译器(gcc版本6.3.0(MinGW.org GCC-6.3.0-1))上编译并运行无任何错误。 我的教授正在使用Dev C ++,而他的编译器是(gcc 4.9.2)。不幸的是,他的版本可以编译而不会出错,但是从Normal Game接受用户输入时也会失败。 我没有足够的调试技巧来找出问题所在。 感谢您的任何帮助,并且解释将令人难以置信。

#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;
}

2 个答案:

答案 0 :(得分:2)

未定义行为(UB)

char y; ... scanf("%s",&y);是未定义的行为,因为"%s"指示scanf()形成由至少一个非空白字符和附加的组成的 string 空字符char y太小了。 @Eugene Sh.

请考虑以下内容。

scanf(" %c",&y);

也存在其他问题,但是上述问题很大。

答案 1 :(得分:0)

userInput()responseUser()函数有缺陷;您需要修复它们。

responseUser()中的致命错误是(如另一个答案中已经提到的chux)是将%s scan conversion specifier应用于单个字符char y

两个函数都忽略输入中的所有问题。他们不检查转换是否成功(scanf()的返回值),而只是假设转换成功。


由于标准输入是行缓冲的,因此最好逐行读取输入。 (也就是说,当用户键入某些内容时,只有在他们按Enter键时,它才会传递给程序。)至少有三种方法可以执行此操作(fgets(),fgetc()循环和scanf() ),但由于OP使用的是scanf(),所以让我们看看如何最好地使用它。

“技巧”正在使用转换模式" %1[^\n]%*[^\n]"。这实际上包括三个部分。前导空格告诉scanf()跳过所有空格,包括换行符。 %1[^\n]是正确的转换,不是换行符(\n)的单字符字符串。目标是至少两个字符组成的数组,因为一个字符本身就是一个字符,而字符串结尾的nul字符(\0则需要另一个字符。 %*[^\n]是跳过的转换,这意味着它实际上不会转换任何内容,但是会跳过任何非换行符。因为它被跳过,并且模式中没有文字或非跳过转换,所以它也是可选的。

这种转换模式的思想是,前面的空间会占用所有空白,包括输入缓冲区中剩余的任何换行符或空行。然后,我们转换一个单字符字符串,并跳过该行上的所有其他内容(但不删除该行末尾的换行符,下一行将使用该换行符作为跳过空白的一部分)。

这里是一个示例函数,该函数返回输入行的第一个字符(转换为大写字母);或EOF(如果发生错误)(例如,用户在一行的开头按 Ctrl + D 结束标准输入):

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

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

    return EOF;
}

请注意,toupper()接受广播到unsigned char 的字符代码。 (其原因归结为以下事实:C标准没有说明char是否为带符号类型,而是定义了<ctype.h>函数,包括isalpha()等。例如采用未签名的char字符代码。)

以下面的小型主游戏菜单程序为例:

#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");
        }
    }
}

如果您进行编译(启用警告;我在所有版本的GCC中都使用-Wall -O2)并运行上述命令,则可以尝试输入错误的选项或按 Ctrl > D

请注意,这确实将输入限制为单个字符。


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");
        }
    }
}

请注意,我们也可以在上方使用case 'C': case 'c': ...来接受大小写字母,并从toupper()函数中删除input()


比方说,您通过允许用户首先设置整数范围来扩展游戏。因此,我们要读取数字作为输入。在这种情况下,scanf()的问题在于,如果用户键入的不是数字,则它将保留在缓冲区中。重复scanf()调用将仅重试在缓冲区中转换相同的输入,并且将失败。 (我们可以通过消耗输入来清除输入缓冲区,直到遇到换行符或EOF为止,但这往往很脆弱,导致程序似乎“忽略”输入行,实际上是在处理输入时落后了一行。)

为避免这种情况,最好改用fgets()方法。幸运的是,这种方法使我们能够接受多种形式的输入。例如:

#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");
    }
}

在这里,我们使用buffer数组作为缓冲区来缓冲整个输入行。 如果有更多输入,则fgets()函数将返回一个指向它的指针。 (如果没有更多输入,或者发生读取错误,则返回NULL。)

如果确实读取一行,我们将首先尝试转换模式" %d %d %c"%c转换单个字符,并用作定点测试:如果整个模式都转换,结果为3,但是第二个整数后面至少有一个额外的非空白字符。如果sscanf()返回两个,则意味着只有两个整数(可能后面跟空格,例如换行符),这就是我们想要的。

如果该模式不起作用,我们接下来尝试" %d %*s %d %c"。相似之处在于,现在两个整数之间有一个跳过的转换%*s。它可以是任何非空格字符序列,例如to..。想法是,这将匹配5 to 151 .. 100之类的输入。

如果这也不起作用,我们尝试" %*s %d %*s %d %c"。我确定您已经知道它的用途:匹配from 5 to 15between 1 and 100之类的输入,但忽略单词,仅转换整数。

如果指定了范围,则函数本身返回0,否则返回非零。您可以像这样使用它:

    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);

OP如何尝试为游戏计时还存在潜在的问题。

clock()函数以每秒CLOCKS_PER_SEC的单位返回经过的CPU时间。 (即,(double)(stopped - started) / (double)CLOCKS_PER_SEC返回started = clock()stopped = clock()之间的CPU时间的秒数。)

问题在于这是CPU时间,而不是实际时间。这只是程序进行实际计算的持续时间,不包括等待用户输入的时间。

此外,它的分辨率可能较低。 (在大多数系统上,CLOCKS_PER_SEC是一百万,但是值clock()会以较大的步长返回增量。也就是说,如果您反复循环调用并打印值,则在某些系统上它将打印相同的值很多次,然后跳到更大的值并在那里停留了很长时间,依此类推;通常精度仅为百分之一秒左右。)

如果我们想高精度地测量挂钟时间,则可以在大多数系统上使用clock_gettime(CLOCK_REALTIME),在Windows上可以使用某些Windows kludge

#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;
}

(如果您不同意“ kludge”,请看一下所需的代码。)

使用上面的代码(包括链接到其他答案所需的其他代码,如果您使用Windows且C库不支持POSIX.1 clock_gettime()),则可以使用例如

double  seconds;

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

这样,我们可以轻松地测量每个游戏的挂钟持续时间。

但是,如果我们想将其划分为用于计算的时间,以及用于等待/处理用户输入的时间,则会出现问题,因为这些事情可能在同一时间发生。现实生活。如果我们想这样做,最好改用例如Ncurses,因此我们可以在每次按键时收到它们。

简而言之,clock()的使用不一定是错误的;只是要测量什么,并将其正确传达给用户的问题。