繁体   English   中英

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

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

我有一个用于显示二分搜索的类的游戏。 代码在我的 ide(atom) 和 gcc 编译器(gcc 版本 6.3.0 (MinGW.org GCC-6.3.0-1))上编译和运行没有错误。 我的教授使用的是 Dev C++,他的编译器是 (gcc 4.9.2)。 不幸的是,他的版本编译时不会出错,但在从普通游戏中获取用户输入时也会失败。 我在调试方面不够熟练,无法找出问题所在。 任何帮助表示赞赏,解释将是令人难以置信的。

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

未定义行为 (UB)

char y; ... scanf("%s",&y); 是未定义的行为"%s"指示scanf() ,以形成至少一个非空格字符的字符串加上一个附加的空字符 char y太小了。 @尤金·什。

请考虑以下内容。

scanf(" %c",&y);

其他问题也存在,但以上是一个大问题。

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

responseUser()的致命错误是(正如chux在另一个答案中已经提到的那样)是将%s扫描转换说明符用于单个字符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': ...以上,接受小写和大写字母,并从input()函数中删除toupper()


假设您通过允许用户首先设置整数范围来扩展游戏。 所以,我们要读取数字作为输入。 在这种情况下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 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;
}

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

使用上面的代码(包括您需要从链接到的其他答案中的附加代码,如果您使用 Windows 并且您的 C 库不支持 POSIX.1clock_gettime() ),您可以使用例如测量挂钟

double  seconds;

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

有了这个,我们可以轻松地测量每场比赛的挂钟持续时间。

但是,如果我们想将其拆分为计算所花费的时间,以及等待/处理用户输入所花费的时间,就会出现问题,因为这些事情在现实生活中可能同时发生 如果我们想这样做,我们最好切换到例如 Ncurses,这样我们就可以在它们发生时接收每个按键。

总之, clock()使用不一定是错误的; 这只是人们试图测量什么并将其正确传达给用户的问题。

暂无
暂无

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

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