繁体   English   中英

无法从 C 中的字符串数组访问空字符串

[英]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 = 0x0p 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未使用,因此(尚未)引发任何问题。
  • argsparseArgs修改为(可能)包含一个 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM