重用动态分配的指针数组时遇到问题: C 语言

Having trouble reusing a dynamically allocted array of pointers: C Language

提问人:Vrooks 提问时间:11/3/2023 最后编辑:Vrooks 更新时间:11/3/2023 访问量:55

问:

.h 文件

#ifndef MDSHELL_H
#define MDSHELL_H

#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <ctype.h>

void getCommand(char * command, char *commandcopy);
void getNumOfTokens(char *command, char ***arrayofTokens, int *numberofTokens);
void getTokens(char *commandcopy, char ***arrayofTokens, int *numberofTokens);


#endif

main.c 文件:

#include <stdio.h>
#include <stdlib.h>
#include "mdshell.h"



int main()
{
  printf("Created by Matthew Lewis\n");
  printf("Type \"help\" to see implemented commands\n\n");

  char *command= NULL;
  char *commandcopy = NULL;
  char **arrayofTokens;
  int numberofTokens;

  while(command == NULL || (strcmp(command, "exit")) != 0)
  {
    if(command == NULL)
    {
      command = malloc(100 * sizeof(char));
      commandcopy = malloc(100 * sizeof(char));
    }
    else{
      free(command);
      free(commandcopy);
      command = malloc(100 * sizeof(char));
      commandcopy = malloc(100 * sizeof(char));
    }
    getCommand(command, commandcopy);
    getNumOfTokens(command, &arrayofTokens, &numberofTokens);
    getTokens(commandcopy, &arrayofTokens, &numberofTokens);

    for(int i = 0; i < numberofTokens; i++)
    { //used for testing to see tokens being returned. will not be in final build
      printf("%s\n", arrayofTokens[i]);
    }

    if((strcmp(command, "help")) == 0)
    {
      printf("pwd - Print the current working directory\n");
      printf("exit - Exit shell\n");
      printf("help - Display this message\n");
      printf("<UNIX cmd>  - Spawn child process, excecute <UNIX cmd> in the foreground\n\n");
    }

    for (int i = 0; i < numberofTokens; i++)
    {
      free(arrayofTokens[i]); //free each element in array of pointers
    }
    free(arrayofTokens);  //frees the array of pointers

  }

mdshell.c 文件

#include "mdshell.h"
#include <stdio.h>
#include <stdlib.h>


void getCommand(char * command, char *commandcopy)
{
  char word[100];
  int size = 0;
  printf("mdshell> ");
  fgets(word, 100, stdin);

  // removes the newline char at the end of whatever user entered
  size = strlen(word);
  word[size - 1] = '\0';

  strcpy((command), word);
  strcpy((commandcopy), word);
}


void getNumOfTokens(char *command, char ***arrayofTokens, int *numberofTokens)
{   
  int isDone = 0;
  char delim[] = " ";
  

  while(!isDone)    // used to find out how many words there are
  {
    if((*numberofTokens) == 0)
    {
      char *word = strtok(command, delim);
      if(word == NULL)
      {
        isDone = 1;
        (*numberofTokens)++;
      }
      else
      (*numberofTokens)++;
    }
    else
    {
      char *word = strtok(NULL, delim);
      if(word == NULL)
      {
        isDone = 1;
        (*numberofTokens)++;
      }
      else
      (*numberofTokens)++;
    }
  }

  //dynamically allocate the array of pointers to the number of elements needed
  (*arrayofTokens) = malloc((*numberofTokens) * sizeof(char *));

}

void getTokens(char *commandcopy, char ***arrayofTokens, int *numberofTokens)
{
  char delim[] = " ";
  int size = 0;
  int arrElement = 0;
  
  char *word = strtok(commandcopy, delim);
  if(word != NULL)
  {
    size = strlen(word);
    (*arrayofTokens)[arrElement] = malloc((size) * sizeof(char));
    strcpy((*arrayofTokens)[arrElement], word);
    arrElement++;
  }
  else
  {
    (*arrayofTokens)[arrElement] = NULL;
  }
  while(word != NULL)
  {
    word = strtok(NULL, delim);
    if(word == NULL)
    {
      (*arrayofTokens)[arrElement] = malloc((size) * sizeof(char));
    }
    else
    {
      size = strlen(word);
      (*arrayofTokens)[arrElement] = malloc((size) * sizeof(char));
      strcpy((*arrayofTokens)[arrElement], word);
      arrElement++;
    }
  }
}

我正在为 linux 编写一个小 shell 程序。我让用户输入一个命令,然后我将其分解为一个以 null 结尾的数组或指向 execvp() 函数所需的 char 的指针。我还没有到那部分。execvp() 需要一个以 null 结尾的字符串数组。我已经将字符串放入一个字符串数组中,除了在字符串末尾获得 null 之外。即使我将该元素分配给 NULL 时,它也只显示空白而不是 (NULL)

这是我得到的输出示例:

输出示例

我有一种感觉,我没有正确地释放arrayofTokens的内存。我正在释放每个动态分配的部分,所以我不知道为什么会出现分段错误。任何帮助将不胜感激,这样我就可以继续完成剩下的任务。祝你今天开心!

我希望能够多次使用 arrayofTokens,同时在我的 shell 中使用不同的命令。这在第一次通过后不起作用。

数组 C 指针 动态内存分配

评论

2赞 BoP 11/3/2023
经典的 C 字符串问题是需要分配 ,因为会添加 a 来终止字符串。因此,每个字符串都需要一个额外的字节。malloc(strlen(s) + 1)strcpy'\0'
0赞 Harith 11/3/2023
请注意,C 标准将其定义为 1,因此您可以将其省略。sizeof(char)
1赞 Vrooks 11/3/2023
BoP 这个,然后在每个循环的开头使 arrElements = 0 允许我多次执行,而不会出现分段错误。谢谢!

答:

0赞 Fe2O3 11/3/2023 #1

很明显,你真的很努力地让它发挥作用。荣誉!

隐藏在成堆代码中的是@BoP在评论中指出的错误。也许教训是努力编写更少的代码。

评论:

  1. 有多少电话可以不被选中?不要以为事情总是有效的!malloc()
  2. MDShell.h - 很好地使用了“标头保护”,但是......避免在不必要时将 #includes 添加到头文件。它表明这些标头的内容与此标头文件的其余部分相关。他们在这里的存在既不必要又分散注意力。
  3. 较短的变量名称更易于扫描。一定要使用描述性的名字,但不要强迫你的读者放慢脚步来检查多音节怪物,尤其是当一个或多个几乎无法区分时。
  4. void getCommand()传递一个缓冲区,但最初使用自己的缓冲区。为什么?而且,如果程序需要第二个副本,则这不是进行克隆的地方。
  5. void getNumOfTokens()接受 3 个参数,然后通过作为函数来浪费其返回值。令牌的 # 可以返回给调用方,并简化函数签名。(循环可以大大简化。voidif/else
  6. void getTokens()...同样,代码重复隐藏了第二个错误。分配的缓冲区大小不仅对于尾随 来说太短,而且 的值应该是 。太多胆怯和试探性的代码使人很难看出出出了什么问题。'\0'argv[argc]NULL
  7. main()???用 和 捶打动态内存表示需要重新考虑代码。malloc()free()
  8. (意见:)仅将块缩进 2 个空格的值是多少?为了能够编写更长的代码行?对于读者来说,长行代码更难扫描(请参阅下面的 code-golf 示例)。我的经验是,缩进 4 个空格可以清楚地清除 、 、 等的从属表达式或块。通过适当的注意和照顾,这减少了大括号细丝的必要性,使代码更容易扫描。只是一个意见......if()for()while()

下面是代码的单个文件重写。我希望这能为你指明一个更好的方向。

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

#define MAX_CMD_LEN 128

char **tokenise( char *str, int *n ) {
    static const char *dlm = " \t";
    char cpy[ MAX_CMD_LEN ]; // disposable copy
    strcpy( cpy, str );

    // chop disposable copy to determine # of tokens (pointers)
    // start with '1' to allow for extra NULL as sentinel at the end of array
    int i = 1;
    for( char *cp = cpy; (cp = strtok(cp, dlm)) != NULL; cp = NULL )
        i++;

    char **arr = calloc( i, sizeof *arr ); // use calloc for consistency
    /* verification of allocation success left as an exercise */

    // chop actual buffer storing pointers to each segment
    i = 0;
    for( char *xp = str; (xp = strtok(xp, dlm)) != NULL; xp = NULL )
        arr[ i++ ] = xp;

    *n = i - 1; // # of tokens is 1 less than pointers assigned
    return arr;
}

int main( void ) {
    for( ;; ) {
        char iBuf[ MAX_CMD_LEN ]; // small stack buffer

        printf( "mdshell> " );
        fgets( iBuf, sizeof iBuf, stdin );
        iBuf[ strcspn( iBuf, "\n" ) ] = '\0'; // trim LF from end

        if( strcmp( iBuf, "help" ) == 0 ) {  // early resolution
            puts(
                "pwd - Print the current working directory\n"
                "exit - Exit shell\n"
                "help - Display this message\n"
                "<UNIX cmd>  - excecute <UNIX cmd> in the foreground\n"
            );
            continue;
        }

        int arrgc;
        char **arrgv = tokenise( iBuf, &arrgc ); // KISS

        // demonstrate results
        for( int i = 0; i <= arrgc; i++)
            printf( "[%d] '%s'\n", i, arrgv[i] );

        free( arrgv ); // easy...
    }

    return 0;
}

结果:

mdshell> fools rush in where wise men fear to go
[0] 'fools'
[1] 'rush'
[2] 'in'
[3] 'where'
[4] 'wise'
[5] 'men'
[6] 'fear'
[7] 'to'
[8] 'go'
[9] '(null)'
mdshell>      // just pressed RETURN here
[0] '(null)'
mdshell>

铌!我的编译器很友善,打印而不是崩溃。您从其他实现中获得的里程可能会有所不同。(null)


编辑:
令人烦恼的是,上面有第二个(一次性)堆栈缓冲区(大小很重要),仅用于切碎和计数令牌,这是一个替代版本(具有代码高尔夫式的倾向。此版本不依赖于编译器令牌,可以移动到实用程序(帮助程序)函数的源文件中。
tokenise()tokenise()MAX_CMD_LEN

简言之,下面将对要检查的缓冲区进行两次传递。第一次传递调用以截断并计算找到的令牌数。然后,从最后一个标记 () 开始,缓冲区被修复,并在填充数组的第二遍之前分配指针数组。这可能会使阅读变得有趣。它是最小的,并且必然会引起人们的注意(和反对意见)。strtok()lw

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

char **tokenise( char *str, int *n ) {
    char *cp, *lw = str, **arr = NULL;
    for( int i = 0, stp = 2; stp--; i = 0 ) {
        for( cp = str; (cp = strtok(cp, " \t")) != NULL; cp = NULL, i++ )
            if( stp ) lw = cp; else arr[ i ] = cp;
        if( stp ) {
            while( lw > str ) if( !*--lw ) *lw = ' '; // 'healing' the buffer
            arr = calloc( (*n = i) + 1, sizeof *arr );
            if( arr == NULL ) return arr; // caller to handle error condition
        }
    }
    return arr;
}

注意:当探测到字符串的开头时,必须小心避免将指针与数组左侧进行比较的 UB 陷阱。

值得将此功能与 OP 的自定义版本进行比较。此版本不对字符串的内容进行任何推定,而 OP 版本的参数名称表明它是单一用途的。努力编写可用于其他情况的实用函数。也许分隔符字符串应该/可以作为另一个参数传递给函数,而不是硬连线的单个字符串......

评论

0赞 Vrooks 11/7/2023
非常感谢您的深入回答。我的导师喜欢指出,我总是把我的代码弄得过于复杂。我只是在学校没有得到足够的练习。我喜欢你重写了程序,并展示了一些关于更好演示的技巧。
0赞 Vrooks 11/7/2023
对我们来说最糟糕的是,我们花了 2 个学期学习 C++,本学期我的 2 门 cs 课程,本学期初的 Linux 课程,我们只是被期望在几节基础知识讲座后了解 C。它肯定变得越来越容易,但它太疯狂了。我们还学习了 HTML、CSS、JS、ML 的基础知识,Linux 的命令,一些基本的 shell 脚本,用 C 编写了我自己的尾部程序,用 C 编写了 Lexer,现在我们只是在一个该死的学期里学习 PROLOG。我的脑海中充满了所有这些信息,它即将爆发。
0赞 Fe2O3 11/8/2023
@Vrooks “最糟糕的是,我们花了 2 个学期学习C++......”最好的办法是改变你的态度......“最好的部分是有机会学习......”...从更远很远的地方,免费建议:认识到这可能是你生命中最美好的岁月,你永远不会回到它们,永远......找到热情,而不是哀叹考验......要么做大,要么回家,正如人们所说......如果你做到了,这一切都是值得的。未来有很多美好的时光,所以享受旅程吧!:-)