[英]Cannot access empty string from array of strings in C
我正在使用 C 中的字符串数组来保存 arguments 给自定义 shell。 我使用以下方法初始化缓冲区数组:
char *args[MAX_CHAR];
一旦我解析了 arguments,我将它们发送到以下 function 以确定 IO 重定向的类型(如果有的话)(这只是第一个用于检查重定向的 DIN ST 重定向功能)。
int parseInputFile(char **args, char *inputFilePath) {
char *inputSymbol = "<";
int isFound = 0;
for (int i = 0; i < MAX_ARG; i++) {
if (strlen(args[i]) == 0) {
isFound = 0;
break;
}
if ((strcmp(args[i], inputSymbol)) == 0) {
strcpy(inputFilePath, args[i+1]);
isFound = 1;
break;
}
}
return isFound;
}
一旦我编译并运行 shell,它就会因 SIGSEGV 而崩溃。 使用 GDB 我确定 shell 在以下行崩溃:
if (strlen(args[i]) == 0) {
这是因为arg[i]
的地址(解析命令后的第一个空字符串)是不可访问的。 这是来自 GDB 和所有相关变量的错误:
(gdb) next
359 if (strlen(args[i]) == 0) {
(gdb) p args[0]
$1 = 0x7fffffffe570 "echo"
(gdb) p args[1]
$2 = 0x7fffffffe575 "test"
(gdb) p args[2]
$3 = 0x0
(gdb) p i
$4 = 2
(gdb) next
Program received signal SIGSEGV, Segmentation fault.
parseInputFile (args=0x7fffffffd570, inputFilePath=0x7fffffffd240 "") at shell.c:359
359 if (strlen(args[i]) == 0) {
我相信返回$3 = 0x0
的p args[2]
意味着由于尚未写入索引,因此它被映射到超出执行范围的地址0x0
。 虽然我不知道为什么这是因为它被声明为缓冲区。 关于如何解决这个问题的任何建议?
编辑:根据 Kaylum 的评论,这是一个最小的可重现示例
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include <sys/stat.h>
#include<readline/readline.h>
#include<readline/history.h>
#include <fcntl.h>
// Defined values
#define MAX_CHAR 256
#define MAX_ARG 64
#define clear() printf("\033[H\033[J") // Clear window
#define DEFAULT_PROMPT_SUFFIX "> "
char PROMPT[MAX_CHAR], SPATH[1024];
int parseInputFile(char **args, char *inputFilePath) {
char *inputSymbol = "<";
int isFound = 0;
for (int i = 0; i < MAX_ARG; i++) {
if (strlen(args[i]) == 0) {
isFound = 0;
break;
}
if ((strcmp(args[i], inputSymbol)) == 0) {
strcpy(inputFilePath, args[i+1]);
isFound = 1;
break;
}
}
return isFound;
}
int ioRedirectHandler(char **args) {
char inputFilePath[MAX_CHAR] = "";
// Check if any redirects exist
if (parseInputFile(args, inputFilePath)) {
return 1;
} else {
return 0;
}
}
void parseArgs(char *cmd, char **cmdArgs) {
int na;
// Separate each argument of a command to a separate string
for (na = 0; na < MAX_ARG; na++) {
cmdArgs[na] = strsep(&cmd, " ");
if (cmdArgs[na] == NULL) {
break;
}
if (strlen(cmdArgs[na]) == 0) {
na--;
}
}
}
int processInput(char* input, char **args, char **pipedArgs) {
// Parse the single command and args
parseArgs(input, args);
return 0;
}
int getInput(char *input) {
char *buf, loc_prompt[MAX_CHAR] = "\n";
strcat(loc_prompt, PROMPT);
buf = readline(loc_prompt);
if (strlen(buf) != 0) {
add_history(buf);
strcpy(input, buf);
return 0;
} else {
return 1;
}
}
void init() {
char *uname;
clear();
uname = getenv("USER");
printf("\n\n \t\tWelcome to Student Shell, %s! \n\n", uname);
// Initialize the prompt
snprintf(PROMPT, MAX_CHAR, "%s%s", uname, DEFAULT_PROMPT_SUFFIX);
}
int main() {
char input[MAX_CHAR];
char *args[MAX_CHAR], *pipedArgs[MAX_CHAR];
int isPiped = 0, isIORedir = 0;
init();
while(1) {
// Get the user input
if (getInput(input)) {
continue;
}
isPiped = processInput(input, args, pipedArgs);
isIORedir = ioRedirectHandler(args);
}
return 0;
}
注意:如果我忘记包含任何重要信息,请告诉我,我可以更新。
当你写
char *args[MAX_CHAR];
您为MAX_CHAR
指向char
的指针分配空间。 您不初始化数组。 如果它是一个全局变量,您将初始化所有指向NULL
的指针,但是您在 function 中执行此操作,因此数组中的元素可以指向任何地方。 在将指针设置为指向允许访问的内容之前,不应取消引用它们。
不过,您也可以在parseArgs()
中执行此操作,您可以在其中执行此操作:
cmdArgs[na] = strsep(&cmd, " ");
这里有两个潜在的问题,但让我们处理你首先遇到的问题。 当strsep()
通过您要拆分的令牌时,它返回NULL
。 你测试它以摆脱parseArgs()
所以你已经知道了。 但是,在您的程序崩溃的地方,您似乎又忘记了这一点。 您在NULL
指针上调用strlen()
,这是不行的。
NULL
和空字符串是有区别的。 空字符串是指向首先具有零字符的缓冲区的指针; 字符串 "" 是指向包含字符 '\0' 的位置的指针。 NULL
指针是指针的特殊值,通常地址为零,这意味着指针不指向任何地方。 显然, NULL
指针不能指向空字符串。 您需要检查参数是否为NULL
,而不是空字符串。
如果您想同时检查NULL
和空字符串,您可以执行类似的操作
if (!args[i] || strlen(args[i]) == 0) {
如果args[i]
是NULL
那么!args[i]
是真的,所以如果你有NULL
或者你有一个指向空字符串的指针,你将进入if
正文。
(您也可以使用!(*args[i])
检查空字符串; *args[i]
是args[i]
指向的第一个字符。因此,如果您有空字符串, *args[i]
为零;零被解释为假,所以!(*args[i])
当且仅当args[i]
是空字符串时才为真。并不是说这更具可读性,但它再次显示了空字符串和NULL
之间的区别。
我提到了解析的 arguments 的另一个问题。 是否存在问题取决于应用程序。 但是,当您使用strsep()
解析字符串时,您会获得指向已解析字符串的指针。 您必须小心不要释放该字符串(它在您的main()
函数中input
)或在解析字符串后修改它。 如果你改变了字符串,你就改变了所有解析字符串的外观。 您不会在您的程序中执行此操作,因此这不是问题,但值得牢记。 如果你想让你解析的 arguments 比现在存活得更久,在通过下一个命令后,你需要复制它们。 传递的下一个命令将像现在一样更改它们。
在main
char input[MAX_CHAR];
char *args[MAX_CHAR], *pipedArgs[MAX_CHAR];
都是未初始化的。 它们包含不确定的值。 这可能是错误的潜在来源,但不是这里的原因,因为
getInput
在任何读取发生之前将input
的内容修改为有效字符串。pipedArgs
未使用,因此(尚未)引发任何问题。args
被parseArgs
修改为(可能)包含一个 NULL 标记值。 没有首先读取任何不确定的指针。 首先,在parseArgs
中可以完全填充args
而无需设置程序其他部分应该依赖的 NULL 标记值。
更深入地看,在parseInputFile
中如下
if (strlen(args[i]) == 0)
与parseArgs
所施加的限制相矛盾,该限制不允许数组中存在空字符串。 更重要的是, args[i]
可能是标记 NULL 值,而strlen
需要一个指向有效字符串的非 NULL 指针。
这个终止条件应该简单地检查args[i]
是否为 NULL。
和
strcpy(inputFilePath, args[i+1]);
args[i+1]
也可能是 NULL 标记值,并且strcpy
还需要指向有效字符串的非 NULL 指针。 当inputSymbol
与数组中的最终标记匹配时,您可以看到这一点。
args[i+1]
也可能评估为args[MAX_ARGS]
,这将超出范围。
此外, inputFilePath
的字符串长度限制为MAX_CHAR - 1
,而args[i+1]
是(可能)一个动态分配的字符串,其长度可能超过此限制。
在getInput
中发现了一些边缘情况:
arguments 到
strcat(loc_prompt, PROMPT);
大小为MAX_CHAR
。 由于loc_prompt
的长度为1
。 如果PROMPT
的长度为MAX_CHAR - 1
,则生成的字符串的长度为MAX_CHAR
。 这不会为 NUL 终止字节留下空间。
readline
在某些情况下可以返回 NULL,所以
buf = readline(loc_prompt);
if (strlen(buf) != 0) {
可以再次将 NULL 指针传递给strlen
。
与以前类似的问题,成功时readline
返回一个动态长度的字符串,并且
strcpy(input, buf);
尝试复制长度大于MAX_CHAR - 1
的字符串可能会导致缓冲区溢出。
buf
是指向由malloc
分配的数据的指针。 不清楚add_history
做了什么,但这个指针最终必须传递给free
。
一些考虑。
首先,初始化数据是一个好习惯,即使它可能无关紧要。
其次,使用常量( #define MAX_CHAR 256
)可能有助于减少幻数,但如果以相同的方式使用,它们会导致您设计程序过于僵化。
考虑构建您的函数以接受一个限制作为参数,并返回一个长度。 这使您可以更严格地跟踪数据的大小,并防止您始终围绕最大的潜在情况进行设计。
像这样设计的一个稍微做作的例子。 我们可以看到find
不必关心是否可能检查MAX_ARGS
元素,因为它被准确地告知有效元素的列表有多长。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_ARGS 100
char *get_input(char *dest, size_t sz, const char *display) {
char *res;
if (display)
printf("%s", display);
if ((res = fgets(dest, sz, stdin)))
dest[strcspn(dest, "\n")] = '\0';
return res;
}
size_t find(char **list, size_t length, const char *str) {
for (size_t i = 0; i < length; i++)
if (strcmp(list[i], str) == 0)
return i;
return length;
}
size_t split(char **list, size_t limit, char *source, const char *delim) {
size_t length = 0;
char *token;
while (length < limit && (token = strsep(&source, delim)))
if (*token)
list[length++] = token;
return length;
}
int main(void) {
char input[512] = { 0 };
char *args[MAX_ARGS] = { 0 };
puts("Welcome to the shell.");
while (1) {
if (get_input(input, sizeof input, "$ ")) {
size_t argl = split(args, MAX_ARGS, input, " ");
size_t redirection = find(args, argl, "<");
puts("Command parts:");
for (size_t i = 0; i < redirection; i++)
printf("%zu: %s\n", i, args[i]);
puts("Input files:");
if (redirection == argl)
puts("[[NONE]]");
else for (size_t i = redirection + 1; i < argl; i++)
printf("%zu: %s\n", i, args[i]);
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.