簡體   English   中英

在C中操縱char *數組時的奇怪行為

[英]Strange behavior when manipulating char* array in C

我正在嘗試為操作系統類創建命令行外殼。 我們的任務之一是創建一個內置的“歷史”命令,該命令打印出在外殼程序中執行的最后10條命令。 這是我為“ history”命令編寫的代碼:

char* cmd_hsitory[10]; // This is a global variable

int add_history(char **args) {
  cmd_history[9] = NULL;
  for(int i = 8; i >= 0; i--) {
    cmd_history[i+1] = cmd_history[i];
  }
  cmd_history[0] = *args;
  return 1;
}

其中char ** args參數是最后執行的命令。 這是打印歷史記錄的函數:

int lsh_history(char **args) {
  printf("Last 10 commands: \n");
  for(int i = 0; i < 10; i++) {
    printf("%s\n", cmd_history[i]);
  }
  return 1;
}

此代碼正在發生一些奇怪的行為。 例如,當我連續運行命令[cd,cd,ls,history]時,這是打印輸出:

Last 10 commands:
ls
ls
cd
(null)
(null)
(null)
(null)
(null)
(null)
(null)

這里的第一個問題是我運行了cd命令兩次,而ls命令只運行了一次。 如果我再次運行“歷史”命令,則會得到:

Last 10 commands:
history
ls
ls
cd
(null)
(null)
(null)
(null)
(null)
(null)

除了2 ls命令與1 cd命令外,這似乎是正確的。

但是,這並不是非常一致,因為有時我會得到一些混淆的命令,並且“ history”命令會顯示幾次。

如果有人告訴我我的代碼有什么明顯的錯誤,那將有很大的幫助。 謝謝!

編輯:這是完整的源代碼:Ps此代碼大多數是從互聯網上提取的(斯蒂芬·布倫南),我正在此基礎上進行學習。 我不會將此代碼作為作業提交。

#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

/*
  Function Declaration for history queue
*/
int add_history(char **args);

/*
  Function Declarations for builtin shell commands:
 */
int lsh_cd(char **args);
int lsh_help(char **args);
int lsh_exit(char **args);
int lsh_history(char **args);

/*
  List of builtin commands, followed by their corresponding functions.
 */
char *builtin_str[] = {
  "cd",
  "help",
  "exit",
  "history"
};

int (*builtin_func[]) (char **) = {
  &lsh_cd,
  &lsh_help,
  &lsh_exit,
  &lsh_history
};

char *cmd_history[10];

int lsh_num_builtins() {
  return sizeof(builtin_str) / sizeof(char *);
}

int add_history(char **args) {
  cmd_history[9] = NULL;
  for(int i = 8; i >= 0; --i) {
    cmd_history[i+1] = cmd_history[i];
  }
  cmd_history[0] = NULL;
  cmd_history[0] = *args;
  return 1;
}

/*
  Builtin function implementations.
*/

/**
  @brief Builtin command: command history.
  @param args List of args. args[0] is "history".
  @return Always returns 1 to continue executing.
*/
int lsh_history(char **args) {
  printf("Last 10 commands: \n");
  for(int i = 0; i < 10; i++) {
    printf("%s\n", cmd_history[i]);
  }
  return 1;
}

/**
   @brief Bultin command: change directory.
   @param args List of args.  args[0] is "cd".  args[1] is the directory.
   @return Always returns 1, to continue executing.
 */
int lsh_cd(char **args)
{
  if (args[1] == NULL) {
    chdir("/Users/Landon/");
  } else {
    if (chdir(args[1]) != 0) {
      perror("lsh");
    }
  }
  return 1;
}

/**
   @brief Builtin command: print help.
   @param args List of args.  Not examined.
   @return Always returns 1, to continue executing.
 */
int lsh_help(char **args)
{
  int i;
  printf("Stephen Brennan's LSH\n");
  printf("Type program names and arguments, and hit enter.\n");
  printf("The following are built in:\n");

  for (i = 0; i < lsh_num_builtins(); i++) {
    printf("  %s\n", builtin_str[i]);
  }

  printf("Use the man command for information on other programs.\n");
  return 1;
}

/**
   @brief Builtin command: exit.
   @param args List of args.  Not examined.
   @return Always returns 0, to terminate execution.
 */
int lsh_exit(char **args)
{
  return 0;
}

/**
  @brief Launch a program and wait for it to terminate.
  @param args Null terminated list of arguments (including program).
  @return Always returns 1, to continue execution.
 */
int lsh_launch(char **args)
{
  pid_t pid;
  int status;

  pid = fork();
  if (pid == 0) {
    // Child process
    if (execvp(args[0], args) == -1) {
      perror("lsh");
    }
    exit(EXIT_FAILURE);
  } else if (pid < 0) {
    // Error forking
    perror("lsh");
  } else {
    // Parent process
    do {
      waitpid(pid, &status, WUNTRACED);
    } while (!WIFEXITED(status) && !WIFSIGNALED(status));
  }

  return 1;
}

/**
   @brief Execute shell built-in or launch program.
   @param args Null terminated list of arguments.
   @return 1 if the shell should continue running, 0 if it should terminate
 */
int lsh_execute(char **args)
{
  int i;

  if (args[0] == NULL) {
    // An empty command was entered.
    return 1;
  }

  for (i = 0; i < lsh_num_builtins(); i++) {
    if (strcmp(args[0], builtin_str[i]) == 0) {
      return (*builtin_func[i])(args);
    }
  }

  return lsh_launch(args);
}

#define LSH_RL_BUFSIZE 1024
/**
   @brief Read a line of input from stdin.
   @return The line from stdin.
 */
char *lsh_read_line(void)
{
  int bufsize = LSH_RL_BUFSIZE;
  int position = 0;
  char *buffer = malloc(sizeof(char) * bufsize);
  int c;

  if (!buffer) {
    fprintf(stderr, "lsh: allocation error\n");
    exit(EXIT_FAILURE);
  }

  while (1) {
    // Read a character
    c = getchar();

    if (c == EOF) {
      exit(EXIT_SUCCESS);
    } else if (c == '\n') {
      buffer[position] = '\0';
      return buffer;
    } else {
      buffer[position] = c;
    }
    position++;

    // If we have exceeded the buffer, reallocate.
    if (position >= bufsize) {
      bufsize += LSH_RL_BUFSIZE;
      buffer = realloc(buffer, bufsize);
      if (!buffer) {
        fprintf(stderr, "lsh: allocation error\n");
        exit(EXIT_FAILURE);
      }
    }
  }
}

#define LSH_TOK_BUFSIZE 64
#define LSH_TOK_DELIM " \t\r\n\a"
/**
   @brief Split a line into tokens (very naively).
   @param line The line.
   @return Null-terminated array of tokens.
 */
char **lsh_split_line(char *line)
{
  int bufsize = LSH_TOK_BUFSIZE, position = 0;
  char **tokens = malloc(bufsize * sizeof(char*));
  char *token, **tokens_backup;

  if (!tokens) {
    fprintf(stderr, "lsh: allocation error\n");
    exit(EXIT_FAILURE);
  }

  token = strtok(line, LSH_TOK_DELIM);
  while (token != NULL) {
    tokens[position] = token;
    position++;

    if (position >= bufsize) {
      bufsize += LSH_TOK_BUFSIZE;
      tokens_backup = tokens;
      tokens = realloc(tokens, bufsize * sizeof(char*));
      if (!tokens) {
        free(tokens_backup);
        fprintf(stderr, "lsh: allocation error\n");
        exit(EXIT_FAILURE);
      }
    }

    token = strtok(NULL, LSH_TOK_DELIM);
  }
  tokens[position] = NULL;
  return tokens;
}

/**
   @brief Loop getting input and executing it.
 */
void lsh_loop(void)
{
  char *line;
  char **args;
  int status;

  do {
    printf("> ");
    line = lsh_read_line();
    args = lsh_split_line(line);
    status = lsh_execute(args);

    add_history(args);
    free(line);
    free(args);
  } while (status);
}

/**
   @brief Main entry point.
   @param argc Argument count.
   @param argv Argument vector.
   @return status code
 */
int main(int argc, char **argv)
{
  // Load config files, if any.

  // Run command loop.
  lsh_loop();

  // Perform any shutdown/cleanup.

  return EXIT_SUCCESS;
}

您需要將arg字符串strdup()移至cmd_history指針,而不僅僅是讓cmd_history指針引用原始命令字符串。 當您釋放(行)時,您會將cmd_history指針引用的內存放回空閑池中。 在循環的下一個迭代中,您可能會或可能不會覆蓋該數據。

您寫了((添加了評論)):

void lsh_loop(void)
{
  ...
    line = lsh_read_line();       //mallocs line 
    args = lsh_split_line(line);  //mallocs args array; consisting of references into line[]
    status = lsh_execute(args);   //no promblem here

    add_history(args);            // ***PERMANETNLY*** stores args[0] into cmd_history[]!!!
    free(line);                   //frees line[]
    free(args);                   //frees the pointer array into freed line[], which was copied to cmd_history[] --> cmd_history[] points to unallocated memory!!!
}

主要問題與strtok()有關:它在原始字符串內將一個指針返回到分散的字符串中:如果以后釋放令牌指針數組或原始字符串,以后再使用它-> UB。 此外,lsh_history()還會殺死最后一個條目, 如果您可以牢固地分配該條目,它將留下內存泄漏。

解決方案:cmd_history[]建立自己的字符串存儲,在lsh_history()復制並正確釋放它們。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM