strtok 和 fgets 的奇怪数组行为
Odd array behaviour with strtok and fgets
我正在开发一个充当 shell 解释器的程序,该解释器读取带有参数的命令并创建一个子进程来执行带有 execvp()
的命令。我坚持做一些字符串操作来收集字符数组 *args[]
,特别是使用 fgets
和 strtok
.
这是我的代码的 MCVE。
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#define MAX_LINE 80
int main(void){
//initialize variables
char *args[MAX_LINE/2 + 1];
char input[MAX_LINE];
//char input[MAX_LINE] = "some sentence unknown"; // <-- this line works fine..
int counter = 0;
printf("COMMANDER>");
fflush(stdout);
//receive input
fgets(input,MAX_LINE,stdin);
//parse input
char *parser;
parser = strtok(input," \r\t");
//parse line
while(parser != NULL){
args[counter] = parser;
counter++;
parser = strtok(NULL," ");
}
//print results
int i = 0;
for(i = 0; i < counter + 1;i++){
printf("1");
printf(" - %d: %s\n",i,args[i]);
}
return 0;
}
这里的问题是输出。当我尝试 运行 时,我得到以下输出:
COMMANDER>some sentence unknown
1 - 0: some
1 - 1: sentence
1 - 2: unknown
1 - 3: (null)
我的问题是空 space。我不知道它从哪里来,无论我做什么它都会出现。
据我所知,它可能是字符串末尾的 \n 字符或其他字符,但是将其作为 execvp(args[0],args)
传递给 execvp
会产生错误,因为它会解释此空行作为“”的参数。
有一行我已经注释掉了,它只是 main
开头的字符串赋值。如果使用此赋值而不是 fgets
程序运行并且我得到所需的输入:
COMMANDER>some sentence unknown
1 - 0: some
1 - 1: sentence
1 - 2: unknown
1 - 3: (null)
感谢阅读。我的 C 有点生疏,所以我自己坚持了几个小时,仍然找不到解决方案。
如果您阅读例如this fgets
reference 你会看到上面写着
Parsing stops if end-of-file occurs or a newline character is found, in which case str will contain that newline character.
[强调我的]
您看到的 "that empty space" 是 fgets
在字符串末尾添加的换行符。
然而,您的代码中存在更严重的问题。
有
char *args[MAX_LINE/2 + 1];
您定义了一个指针数组,但是您让这个数组未初始化。在 C 中,未初始化的局部(和非静态)变量实际上 是 未初始化的。它们的内容将是不确定的并且看起来几乎是随机的。
更具体地说,您碰巧在 args[counter]
处得到一个空指针纯属运气。
在未初始化的情况下尝试以任何方式使用此指针将导致 undefined behavior。
简单的解决方案是将数组显式初始化为充满空指针:
char *args[MAX_LINE/2 + 1] = { NULL };
以上将"zero-initialize"所有元素,这对于指针意味着它们将是NULL
。
fgets()
读取的行包含尾随换行符 '\n'
。您必须将其包含在 strtok()
识别为分隔符的字符列表中。此外,您必须将相同的列表传递给两个调用。
您还忘记将偏移量 count
处的条目设置为 NULL
。具有自动存储的本地对象未初始化。另请注意,您不应为 %s
转换说明符将空指针传递给 printf
:它具有未定义的行为,尽管某些实现会测试参数并输出特定的字符串。
这是更正后的版本:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#define MAX_LINE 80
int main(void){
char *args[MAX_LINE/2 + 1];
char input[MAX_LINE];
int counter;
printf("COMMANDER>");
fflush(stdout);
//receive input
if (fgets(input, sizeof input, stdin)) {
//parse input
char *parser;
parser = strtok(input, " \f\n\r\t");
//parse line
count = 0;
while (parser != NULL) {
args[counter] = parser;
counter++;
parser = strtok(NULL, " \f\n\r\t");
}
args[counter] = NULL;
//print results
int i;
for (i = 0; i <= counter; i++) {
printf("1 - %d: %s\n", i, args[i] ? args[i] : "(null)");
}
}
return 0;
}
我正在开发一个充当 shell 解释器的程序,该解释器读取带有参数的命令并创建一个子进程来执行带有 execvp()
的命令。我坚持做一些字符串操作来收集字符数组 *args[]
,特别是使用 fgets
和 strtok
.
这是我的代码的 MCVE。
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#define MAX_LINE 80
int main(void){
//initialize variables
char *args[MAX_LINE/2 + 1];
char input[MAX_LINE];
//char input[MAX_LINE] = "some sentence unknown"; // <-- this line works fine..
int counter = 0;
printf("COMMANDER>");
fflush(stdout);
//receive input
fgets(input,MAX_LINE,stdin);
//parse input
char *parser;
parser = strtok(input," \r\t");
//parse line
while(parser != NULL){
args[counter] = parser;
counter++;
parser = strtok(NULL," ");
}
//print results
int i = 0;
for(i = 0; i < counter + 1;i++){
printf("1");
printf(" - %d: %s\n",i,args[i]);
}
return 0;
}
这里的问题是输出。当我尝试 运行 时,我得到以下输出:
COMMANDER>some sentence unknown
1 - 0: some
1 - 1: sentence
1 - 2: unknown
1 - 3: (null)
我的问题是空 space。我不知道它从哪里来,无论我做什么它都会出现。
据我所知,它可能是字符串末尾的 \n 字符或其他字符,但是将其作为 execvp(args[0],args)
传递给 execvp
会产生错误,因为它会解释此空行作为“”的参数。
有一行我已经注释掉了,它只是 main
开头的字符串赋值。如果使用此赋值而不是 fgets
程序运行并且我得到所需的输入:
COMMANDER>some sentence unknown
1 - 0: some
1 - 1: sentence
1 - 2: unknown
1 - 3: (null)
感谢阅读。我的 C 有点生疏,所以我自己坚持了几个小时,仍然找不到解决方案。
如果您阅读例如this fgets
reference 你会看到上面写着
Parsing stops if end-of-file occurs or a newline character is found, in which case str will contain that newline character.
[强调我的]
您看到的 "that empty space" 是 fgets
在字符串末尾添加的换行符。
然而,您的代码中存在更严重的问题。
有
char *args[MAX_LINE/2 + 1];
您定义了一个指针数组,但是您让这个数组未初始化。在 C 中,未初始化的局部(和非静态)变量实际上 是 未初始化的。它们的内容将是不确定的并且看起来几乎是随机的。
更具体地说,您碰巧在 args[counter]
处得到一个空指针纯属运气。
在未初始化的情况下尝试以任何方式使用此指针将导致 undefined behavior。
简单的解决方案是将数组显式初始化为充满空指针:
char *args[MAX_LINE/2 + 1] = { NULL };
以上将"zero-initialize"所有元素,这对于指针意味着它们将是NULL
。
fgets()
读取的行包含尾随换行符 '\n'
。您必须将其包含在 strtok()
识别为分隔符的字符列表中。此外,您必须将相同的列表传递给两个调用。
您还忘记将偏移量 count
处的条目设置为 NULL
。具有自动存储的本地对象未初始化。另请注意,您不应为 %s
转换说明符将空指针传递给 printf
:它具有未定义的行为,尽管某些实现会测试参数并输出特定的字符串。
这是更正后的版本:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#define MAX_LINE 80
int main(void){
char *args[MAX_LINE/2 + 1];
char input[MAX_LINE];
int counter;
printf("COMMANDER>");
fflush(stdout);
//receive input
if (fgets(input, sizeof input, stdin)) {
//parse input
char *parser;
parser = strtok(input, " \f\n\r\t");
//parse line
count = 0;
while (parser != NULL) {
args[counter] = parser;
counter++;
parser = strtok(NULL, " \f\n\r\t");
}
args[counter] = NULL;
//print results
int i;
for (i = 0; i <= counter; i++) {
printf("1 - %d: %s\n", i, args[i] ? args[i] : "(null)");
}
}
return 0;
}