strcat() 和 read() 如何与 C 中的 '\0' 一起使用

How do strcat() and read() work with '\0' in C

首先是我的全部代码:

 1. #include <stdio.h>
 2. #include <stdlib.h>
 3. #include <unistd.h>
 4. #include <sys/wait.h>
 5. #include <string.h>
 6. int main(int argc, char *argv[]) {
 7.     int p[2]; // p[0]: file descriptor for read end of pipe
 8.               // p[1]: file descriptor for write end of pipe
 9.     if (pipe(p) < 0) exit(1);
 10.    int rc1 = fork();
 11.    if (rc1 < 0){ fprintf(stderr, "fork error\n"); }
 12.    else if (rc1 == 0){ write(p[1], "1st child output",
 13.                              sizeof("1st child output")); }
 14.    else{
 15.        int rc2 = fork();
 16.        if (rc2 < 0){ fprintf(stderr, "fork error\n"); }
 17.        else if (rc2 == 0){
 18.            printf("2st child output\n");
 19.            char *_1st_child_out;
 20.            read(p[0], _1st_child_out, sizeof("1st child output"));
 21.            strcat(_1st_child_out, ", AFTER PIPE YA FOOL");
 22.            printf("%s\n", _1st_child_out);
 23.        }
 24.    }
 25. }

如果我初始化 19:13:

char *_1st_child_out;

带有 '\0' 或 NULL, 字符串保持为空并且 22:13:

printf("%s\n", _1st_child_out);

什么都不打印,那么 strcat() 和 read() 是如何工作的呢? 我不应该在调用它们之前插入任何空终止符吗? 垃圾值呢?

您的第 19 行有一个未指向任何已分配内存的指针。将其更改为字符数组即可解决问题。

char _1st_child_out[100];

此外,为了安全起见,请不要使用strcatstrcpy 等所有 str 函数都不检查目标边界。他们都有一个 n 版本。 strcat 应替换为 strncat,它将采用第三个参数来指定 cat 的最大长度,因此不会发生缓冲区溢出。

READ(2)不关心'\0',不关心他读到的any值。 STRCAT(3) 将从给定 const char *src 指针末尾的 '\0' 读取和写入(操作,而不是函数)。

据我所知,_1st_child_out 是一个未初始化的指针。 在 READ(2) 的某个地方会有一些东西

dest[i] = array_bytes[i];

但是在这里,你的 char *_1st_child_out 被赋予了一个总的 "random" 值,你将只是在你的内存中随机写入某个地方,大多数时候它会导致分段错误,并且你的程序会崩溃。

那么你需要注意你在这里使用 sizeof 运算符, 你给他 "1st child output" ,这将在 C 中被解释为 const char[] 并将 sizeof("1st child output") 替换为值 17(16 个字符和 '\0')。 你需要在其中指定一个类型,或者一个变量。

对于数组,如type array[x]sizeof(array)将与sizeof(type) * x相同。

为了修复你的程序,尝试创建一个静态分配的缓冲区,比如 char _1st_child_out[x]; 其中 'x' 是缓冲区的大小(以字节为单位)。 或者尝试使用 MALLOC(3)。 然后修复 READ(2).

的第三个参数

当您使用 STRCAT(3) 时,您需要知道如果给定目标缓冲区的大小不足以容纳 ", AFTER PIPE YA FOOL" 您的程序很可能会崩溃。

你的代码中有一些错误,这是我的观察。

案例 1:- 在您的代码中,您调用了 fork() 两次,管道写入端 p[1] 包含一些数据 1st child output in第一个 fork() rc1 进程,但您的代码试图在第二个 rc2 进程中读取表单 p[0]

您应该检查 read() return 值是否成功,或者它是否正在从 wrong/uninitialized 文件描述符中读取。这个

    char *_1st_child_out = NULL;
    /* for process rc2, p[0] contains nothing, so what read() will read from p[0] ?? */
    int ret = read(p[0], _1st_child_out, sizeof("1st child output"));
    if(ret == -1) {
          perror("read");
          /* error handling */
   }

由于数据写入 p[1] 是在 rc1 进程中,而不是在 rc2 进程中,但是当您尝试从 p[0] 读取数据时,它会给您

read: Bad address

情况 2 :- 克服上述问题的一种方法是

int main(int argc, char *argv[]) {
        int p[2]; // p[0]: file descriptor for read end of pipe
        // p[1]: file descriptor for write end of pipe
        if (pipe(p) < 0) exit(1);
        int rc1 = fork();
        if (rc1 < 0){ fprintf(stderr, "fork error\n"); }
        else if (rc1 == 0){
                write(p[1], "1st child output",sizeof("1st child output"));
        }
        else{
                char *_1st_child_out = NULL;

                /* read() will read from p[0] and store into _1st_child_out but _1st_child_out not holding any valid memory ? So it causes Undefined behavior */
                int ret = read(p[0], _1st_child_out, sizeof("1st child output"));
                if(ret == -1) {
                perror("read");
                /* error handling */
                }
                strcat(_1st_child_out, ", AFTER PIPE YA FOOL");
                printf("%s\n", _1st_child_out);
        }
        return 0;
}

这里_1st_child_out是一个指针,指针应该有有效的内存位置。您可以使用 NULL 进行初始化,即 (void*)0,这是有效的,但使用 [=30=] 则无效,因为它只是一个字符。

但是当你用 NULL 初始化 _1st_child_out 并从 p[0] 读取数据并存储到 _1st_child_out 时,它会存储什么?它会导致分段错误,它也是 未定义的行为

所以最好为 _1st_child_out 动态分配内存,然后调用 read() 或者像

这样创建堆栈分配数组
char _1st_child_out[10];

这是示例工作代码

int main(int argc, char *argv[]) {
        int p[2]; // p[0]: file descriptor for read end of pipe
        // p[1]: file descriptor for write end of pipe
        if (pipe(p) < 0) exit(1);
        int rc1 = fork();
        if (rc1 < 0){ fprintf(stderr, "fork error\n"); }
        else if (rc1 == 0){
                write(p[1], "1st child output",sizeof("1st child output"));
        }
        else{

                char *_1st_child_out = malloc(BYTE); /* define BYTE value as how much memory needed, and free the dynamically allocated memory once job is done */
                int ret = read(p[0], _1st_child_out, sizeof("1st child output"));
                if(ret == -1) {
                        perror("read");
                        /* error handling */
                }
                /* make sure _1st_child_out has enough memory space to concatenate */
                strcat(_1st_child_out, ", AFTER PIPE YA FOOL");
                printf("%s\n", _1st_child_out);
        }
        return 0;
}

注意:使用 strncat() 而不是 strcat(),原因可以从 strcat() https://linux.die.net/man/3/strcat 的手册页中找到

The strcat() function appends the src string to the dest string, overwriting the terminating null byte ('[=43=]') at the end of dest, and then adds a terminating null byte. The strings may not overlap, and the dest string must have enough space for the result. If dest is not large enough, program behavior is unpredictable; buffer over‐ runs are a favorite avenue for attacking secure programs.

   The strncat() function is similar, except that

   *  it will use at most n bytes from src; and

   *  src does not need to be null-terminated if it contains n or more bytes.

   As with `strcat()`, the resulting string in dest is always null-terminated.