【问题标题】:Reading continuously from stdin从标准输入连续读取
【发布时间】:2021-08-03 02:34:49
【问题描述】:

我刚开始学习 C 很抱歉,如果我错过了一些明显的东西。作为一种实践,我想编写自己的 posix cat 并且几乎可以正常工作,但是当没有参数时,它不会从 stdin 读取多个文件。

这是预期的行为(忽略这个,因为这是我不知道的 Zsh 功能):

$ cat < hello.txt < world.txt
hello
world
$ 

但是我的cat 因“分段错误”而崩溃(我有一个没有崩溃但也没有输出任何内容的版本)

代码如下:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
    char opt;
    while ((opt = getopt(argc, argv, "u")) != EOF) {
        switch(opt) {
            case 'u':
                /* Make the output un-buffered */
                setbuf(stdout, NULL);
                break;
            default:
                break;
        }
    }

    argc -= optind;
    argv += optind;

    int i = 0, fildes, fs = 0;

    do {
        /* Check for operands, if none or operand = "-". Read from stdin */
        if (argc == 0 || !strcmp(argv[i], "-")) {
            fildes = STDIN_FILENO;
        } else {
            fildes = open(argv[i], O_RDONLY);
        }

        /* Check for directories */
        struct stat fb;
        if (!fstat(fildes, &fb) && S_ISDIR(fb.st_mode)) {
            fprintf(stderr, "cat: %s: Is a directory\n", argv[i]);
            i++;
            continue;
        }

        /* Get file size */
        fs = fb.st_size;

        /* If bytes are read, write them to stdout */
        char buf[fs];
        while ((read(fildes, buf, fs)) > 0)
            write(STDOUT_FILENO, buf, fs);

        /* Close file if it's not stdin */
        if (fildes != STDIN_FILENO)
            close(fildes);

        i++;
    } while (argv[i] != NULL || argc == 0);

    return 0;
}

【问题讨论】:

  • 您没有传递任何参数。 '&lt;' 字符从文件重定向标准输入。 shell 不会将其作为命令行参数传递。
  • hello.txtworld.txt文件的内容是什么?
  • 那么cat &lt; hello.txt &lt; world.txt不可能打印hello world。它应该只打印world
  • @KamilCuk 然后 GNU cat 是 magick 或 idk imgur.com/a/S99dFkj
  • @stark 猜测一下,shell 是 zsh,其中(除非 multios option 关闭)foo &lt;a &lt;b 相当于 cat a b | foofoo &gt;a &gt;b 相当于 foo | tee a b &gt;/dev/null

标签: c linux segmentation-fault stdin do-while


【解决方案1】:

当您使用 gcc 编译代码时使用 -fsanitize=address 代码显示:

AddressSanitizer:DEADLYSIGNAL
=================================================================
==604950==ERROR: AddressSanitizer: stack-overflow on address 0x7ffea6d94000 (pc 0x55b9a63dfb57 bp 0x7ffea6d90d20 sp 0x7ffea6d90b80 T0)
    #0 0x55b9a63dfb57 in main /tmp/10/1.c:56
    #1 0x7f1403c17b24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24)
    #2 0x55b9a63df3dd in _start (/tmp/.ccrun.604893.exefile+0x23dd)

SUMMARY: AddressSanitizer: stack-overflow /tmp/10/1.c:56 in main
==604950==ABORTING

Th sn-p:

do {
   ...
   ++i;
} while (argv[i] != NULL || argc == 0);       // HERE!

argc 总是0,所以while(...) 总是在执行,而++i 总是在递增。 argc[...] 被越界访问,直到出现段错误。

我觉得你应该改成} while (argv[i] != NULL &amp;&amp; argc != 0);

【讨论】:

  • 这确实解决了错误,如果我没记错的话,while(i &lt; argc) 也应该如此。但是我仍然对标准输入有问题:我无法通过管道连接到我的cat,并且当标准输入没有不应该的参数或输入时它会退出。 IDK 管道发生了什么,IDK 如何解决没有潜在堆栈溢出的第二个问题,欢迎任何指针(或者我应该创建一个新问题?)
  • 应该 argc==0, argv[i] do { ... ++i } while (argv[i] != NULL &amp;&amp; argc != 0); 是 UB 和 argv[i]。最好倒序或使用while {}