提问人:Vrooks 提问时间:11/3/2023 最后编辑:Vrooks 更新时间:11/3/2023 访问量:55
重用动态分配的指针数组时遇到问题: C 语言
Having trouble reusing a dynamically allocted array of pointers: C Language
问:
.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 中使用不同的命令。这在第一次通过后不起作用。
答:
很明显,你真的很努力地让它发挥作用。荣誉!
隐藏在成堆代码中的是@BoP在评论中指出的错误。也许教训是努力编写更少的代码。
评论:
- 有多少电话可以不被选中?不要以为事情总是有效的!
malloc()
- MDShell.h - 很好地使用了“标头保护”,但是......避免在不必要时将 #includes 添加到头文件。它表明这些标头的内容与此标头文件的其余部分相关。他们在这里的存在既不必要又分散注意力。
- 较短的变量名称更易于扫描。一定要使用描述性的名字,但不要强迫你的读者放慢脚步来检查多音节怪物,尤其是当一个或多个几乎无法区分时。
void getCommand()
传递一个缓冲区,但最初使用自己的缓冲区。为什么?而且,如果程序需要第二个副本,则这不是进行克隆的地方。void getNumOfTokens()
接受 3 个参数,然后通过作为函数来浪费其返回值。令牌的 # 可以返回给调用方,并简化函数签名。(循环可以大大简化。void
if/else
void getTokens()
...同样,代码重复隐藏了第二个错误。分配的缓冲区大小不仅对于尾随 来说太短,而且 的值应该是 。太多胆怯和试探性的代码使人很难看出出出了什么问题。'\0'
argv[argc]
NULL
main()
???用 和 捶打动态内存表示需要重新考虑代码。malloc()
free()
- (意见:)仅将块缩进 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 版本的参数名称表明它是单一用途的。努力编写可用于其他情况的实用函数。也许分隔符字符串应该/可以作为另一个参数传递给函数,而不是硬连线的单个字符串......
评论
:-)
评论
malloc(strlen(s) + 1)
strcpy
'\0'
sizeof(char)