【问题标题】:Fgets() results in segfault on one OS, but not the other [duplicate]Fgets() 导致一个操作系统上的段错误,但不是另一个[重复]
【发布时间】:2019-05-03 17:32:40
【问题描述】:

我正在编写一个简单的 unix shell 程序,但遇到了由 fgets() 调用引起的分段错误的问题。通过调用,我试图从命令行获取用户输入并将其保存以供进一步使用。应该很简单。在 MacOS 上,代码编译时会发出一个警告,但执行得很好。在我的 Ubuntu 虚拟机上进行测试时,我遇到了一个段错误,我目前已将其范围缩小到特定的 fgets() 调用。

我对 DDD 不太熟悉,但我能够使用它和一些简单的打印语句来确定它的 fgets() 调用给我带来了麻烦。我检查了分配调用的指针是否已正确分配。尽管我的两台机器运行不同版本的 gcc,但我很困惑为什么我只在一个系统上而不是在两个系统上都出现段错误。

下面是我的源代码,我遇到问题的具体函数是 parse() 函数。这不是完整或未完成的代码,但我希望不断提示用户输入,从命令行接收输入,保存此输入并将其拆分为令牌以传递给 execute() 以供进一步使用.我已被告知并了解我目前存在内存泄漏和范围错误。我仍然不确定为什么在 Ubuntu 上,我第一次调用 parse() 并使其进入函数内部的 fgets() 调用时出现分段错误。


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define PROMPT "MyShell> "
#define MAX_SIZE 256
#define EXIT_CMD "exit"



/**
  @brief Takes a pointer as an argument and checks whether or not it is NULL
         (hasn't been properly allocated in memory). If the pointer is NULL,
         behavior is undefined, so an error message is displayed to the user
         and the program is terminated.
*/
void validateMemoryAllocation(char* pointer)
{
    if (pointer == NULL)
    {
        printf("%s", "Fatal Error: Failed to allocate memory to save command input. Exiting...\n");
        exit(0);
    }

}

/**
  @brief Fork a child to execute the command using execvp. The parent should wait for the child to terminate
  @param args Null terminated list of arguments (including program).
  @return returns 1, to continue execution and 0 to terminate the MyShell prompt.
 */
int execute(char **args)
{
    if (strcmp(args[0], "exit") == 0) // Check for exit command.
    {
        printf("Exit received. Terminating MyShell...\n");
        return 1;   // Return to main with exit value to terminate the program.
    } else  // Not exit command, proceed attempting to execute.
    {

    }

    return 0; // Return 0 and continue MyShell.
}


/**
  @brief gets the input from the prompt and splits it into tokens. Prepares the arguments for execvp
  @return returns char** args to be used by execvp
 */
char** parse(void)
{
    char *rawInput, *inputDup, *token;
    int validCheck, argCount, i, newLineLocation;

    /* Save the entire line of user input. */
    rawInput = malloc(sizeof(char) * MAX_SIZE);
    validateMemoryAllocation(rawInput);
    fgets(rawInput, MAX_SIZE, stdin);
    inputDup = strdup(rawInput); /* Duplicate the string for modification. */

    /* First loop: Count number of total arguments in user input. */
    argCount = 0;
    while( (token = strsep(&inputDup, " ")) != NULL)
    {
        argCount++;
    }

    /* Create array to hold individual command arguments. */
    char* tokenArray[argCount];

    /* Second loop: save tokens as arugments in tokenArray. */
    for (i = 0; i < argCount; i++)
    {
        token = strsep(&rawInput, " ");
        tokenArray[i] = token;
    }

    /**
      Before returning the arguments, trim the dangling new line
      character at the end of the last argument.
    */
    tokenArray[argCount - 1] = strtok(tokenArray[argCount - 1], "\n");

    return tokenArray;

}


/**
   @brief Main function should run infinitely until terminated manually using CTRL+C or typing in the exit command
   It should call the parse() and execute() functions
   @param argc Argument count.
   @param argv Argument vector.
   @return status code
 */
int main(int argc, char **argv)
{
    int loopFlag = 0;
    char** input;

    /* Loop to continue prompting for user input. Exits with proper command or fatal failure. */
    while (loopFlag == 0)
    {
        printf("%s", PROMPT);   // Display the prompt to the user.
        input = parse();        // Get input.
        loopFlag = execute(input);   // Execute input.
    }


    return EXIT_SUCCESS;
}

我希望将用户输入保存到字符串指针 rawInput,这在 MacOS 上是这种情况,但在 Ubuntu 上不是。

编辑:如果有帮助,这里是使用的两个系统的一些示例输出。我知道我有一些内存泄漏需要修补。

MacOS

D-10-16-18-145:a1 user$ ./myshell 
MyShell> hello
MyShell> darkness
MyShell> my
MyShell> old
MyShell> friend
MyShell> exit
Exit received. Terminating MyShell...
D-10-16-18-145:a1 user$ 

Ubuntu

MyShell> hello
Segmentation fault (core dumped)

【问题讨论】:

  • 您的rawInput 是否以空值结尾?段错误不是由fgets 引入的,而是由其他东西引入的
  • 我认为fgets() 调用没有问题。问题可能出在其他地方。一旦你做了一些导致未定义行为的事情,它可能会导致程序中其他任何地方的不当行为。
  • @EugeneSh。 fgets() 不需要缓冲区以空值终止,甚至不需要初始化。
  • @barmar 但strdup 可以
  • @EugeneSh。 fgets() 总是添加一个空终止符。

标签: c segmentation-fault fgets


【解决方案1】:

在做

 char** parse(void)
 {
   char* tokenArray[argCount];
   ...
   return tokenArray;
 }

你返回一个本地数组的地址,当你读到里面的时候,行为将是未定义的

不要将tokenArray放入栈中,分配它,所以替换

 char* tokenArray[argCount];

通过

char** tokenArray = malloc(argCount * sizeof(char *));

如何知道函数外的条目数?

多分配一个条目并在末尾放置一个 NULL 或将 argCount 作为输出变量可能是个好主意


还要注意您的inputDup = strdup(rawInput); 会造成内存泄漏,释放

【讨论】:

  • 嘿@bruno 我明白你在说什么。这绝对是我需要解决的问题,如果我正确地关注你,一旦我从函数返回,我将丢失该信息。谢谢你。目前在 Ubuntu 上,我无法通过 fgets() 调用。我很感激并将听取您的建议。
  • @B_Ran "我无法通过 fgets() 调用" 第一次你使用 parse 或者你叫了几次?
  • 第一次调用 parse() 时,我无法通过 fgets 调用。我已经包含了一个显示两个系统的示例输出的编辑。万事如意
  • @B_Ran 你在调用 parse 之前在做什么?可能您已经“破坏”了内存。在 ubuntu 下使用 valgrind 来执行你的程序。顺便说一句,你说你在编译时有一个警告,哪一个?我鼓励您使用带有 gcc 的选项 -pedantic -Wall -Werror 进行编译
  • 我必须为自己的固执道歉。我理解您之前告诉我的内容,但认为由于我在您建议的更改之前发生的错误,一定有其他问题。我采纳了您的建议,现在它适用于两个系统。我仍然不确定它究竟为什么会修复它所做的事情,但我感谢您的帮助和多项建议。一切顺利。
猜你喜欢
  • 2012-01-07
  • 1970-01-01
  • 2013-02-03
  • 1970-01-01
  • 2016-02-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-01
相关资源
最近更新 更多