Git   发布时间:2022-04-04  发布网站:大佬教程  code.js-code.com
大佬教程收集整理的这篇文章主要介绍了在STDOUT和STDIN的文件描述符上执行库函数的奇怪行为大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。

在我作为C程序员的几年中,我一直对标准stream文件描述符感到困惑。 一些地方,如维基百科[1] ,说:

在C编程语言中,标准input,@L_675_1@和错误stream分别附加到现有的Unix文件描述符0,1和2上。

这是由unistd.h备份的:

/* Standard file descriptors. */ #define STDIN_FILENO 0 /* Standard input. */ #define STDOUT_FILENO 1 /* Standard output. */ #define STDERR_FILENO 2 /* Standard error output. */

但是,这个代码(在任何系统上):

开源Clearcase的替代品

存放一切从stream浪者箱子

如何让Node.js将套接字消息分成更小的块

Java似乎正在发送回车到一个subprocess?

访问ntfsstream非常长的文件名失败

write(0,"Hello,World!n",14);

将打印Hello,World! (和一个换行符)到STDOUT 。 这很奇怪因为STDOUT的文件描述符应该是1. write文件描述符1也打印到STDOUT 。

文件描述符0上执行ioctl更改标准input[2] ,并在文件描述符1上更改标准@L_675_1@。 但是,在0或1上执行termios函数会改变标准input[3] [4] 。

我对文件描述符1和0的行为非常困惑。有谁知道为什么:

write 1或0写入标准@L_675_1@?

执行ioctl on 1会修改标准@L_675_1@,并在0上修改标准input,但在1或0上执行tcsetattr / tcgetattr可用于标准input?

Vagrant的Ubuntu 16.04 vagrantfile认密码

tesTing厨房login命令失败

如何tesTingWindows Server是否完全更新? (试图创build一个更新/重启循环脚本)

无业游民无法胜利10

@L_489_22@ 301下载端口由stream浪者转发

首先回顾一下所涉及的一些关键概念:

文件描述@H_419_46@

在操作系统内核中,每个文件,管道端点,套接字端点,打开的设备节点等都有一个文件描述 。 内核使用这些来跟踪文件中的位置,标志(读,写,附加,关闭执行),记录锁等等。

文件描述是内核的内核,不属于任何特定的进程(在典型的实现中)。

文件描述符@H_419_46@

从处理的角度来看,文件描述符是识别打开的文件,管道,套接字,FIFO或设备的整数。

操作系统内核为每个进程保留一个描述符表。 进程使用的文件描述符只是该表的索引。

文件描述符表中的条目是指内核文件描述。

每当进程使用dup()或dup2()复制文件描述符时,内核只复制该进程的文件描述符表中的条目; 它不重复它自己保存的文件描述。

当进程分叉时,子进程获取自己的文件描述符表,但是这些条目仍指向完全相同的内核文件描述。 (这实质上是一个浅拷贝 ,所有的文件描述符表项都是文件描述的引用,引用被复制;引用的目标保持不变。

当进程通过Unix域套接字辅助消息将文件描述符发送到另一个进程时,内核实际上在接收器上分配一个新的描述符,并复制所传送的描述符引用的文件描述。

尽管“文件描述符”和“文件描述”如此相似,但有点令人困惑,这一切都运行良好。

所有这些与OP看到的效果有什么关系?

每当创建新进程时,通常会打开目标设备,管道或套接字,并将描述符dup2()打开为标准输入,标准@L_675_1@和标准错误。 这导致所有三个标准描述符引用相同的文件描述 ,因此无论使用一个文件描述符有效的操作,使用其他文件描述符也是有效的。

当在控制台上运行程序时这是最常见的,因为这三个描述符都明确地指向相同的文件描述; 该文件描述描述了伪终端字符设备的从端。

虑下面的程序run.c@H_419_46@ :

#define _POSIX_C_sourcE 200809L #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <String.h> #include <errno.h> static void wrerrp(const char *p,const char *q) { while (p < q) { ssize_t n = write(STDERR_FILENO,p,(size_t)(q - p)); if (n > 0) p += n; else return; } } static inline void wrerr(const char *s) { if (s) wrerrp(s,s + strlen(s)); } int main(int argc,char *argv[]) { int fd; if (argc < 3) { wrerr("nUsage: "); wrerr(argv[0]); wrerr(" FILE-OR-DEVICE COMMAND [ ARGS ... ]nn"); return 127; } fd = open(argv[1],O_RDWR | O_CREAT,0666); if (fd == -1) { const char *msg = strerror(errno); wrerr(argv[1]); wrerr(": CAnnot open file: "); wrerr(msg); wrerr(".n"); return 127; } if (dup2(fd,STDIN_FILENO) != STDIN_FILENO || dup2(fd,STDOUT_FILENO) != STDOUT_FILENO) { const char *msg = strerror(errno); wrerr("CAnnot duplicate file descriptors: "); wrerr(msg); wrerr(".n"); return 126; } if (dup2(fd,STDERR_FILENO) != STDERR_FILENO) { /* We might not have standard error anymore.. */ return 126; } /* Close fd,since it is no longer needed. */ if (fd != STDIN_FILENO && fd != STDOUT_FILENO && fd != STDERR_FILENO) close(fd); /* Execute the command. */ if (strchr(argv[2],'/')) execv(argv[2],argv + 2); /* Command has /,so it is a path */ else execvp(argv[2],argv + 2); /* command has no /,so it is a filename */ /* Whoops; Failed. But we have no stderr left.. */ return 125; }

它需要两个或更多的参数。 第一个参数是文件或设备,第二个参数是命令,剩下的参数提供给命令。 运行该命令,将所有三个标准描述符重定向到第一个参数中指定的文件或设备。 你可以用gcc编译上面的例子

gcc -Wall -O2 run.c -o run

我们来写一个小测试工具report.c@H_419_46@ :

#define _POSIX_C_sourcE 200809L #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <String.h> #include <stdio.h> #include <errno.h> int main(int argc,char *argv[]) { char buffer[16] = { "n" }; ssize_t result; FILE *out; if (argc != 2) { fprintf(stderr,"nUsage: %s FILenamenn",argv[0]); return EXIT_FAILURE; } out = fopen(argv[1],"w"); if (!out) return EXIT_FAILURE; result = write(STDIN_FILENO,buffer,1); if (result == -1) { const int err = errno; fprintf(out,"write(STDIN_FILENO,1) = -1,errno = %d (%s).n",err,strerror(err)); } else { fprintf(out,1) = %zd%sn",result,(result == 1) ? ",success" : ""); } result = read(STDOUT_FILENO,"read(STDOUT_FILENO,success" : ""); } result = read(STDERR_FILENO,"read(STDERR_FILENO,success" : ""); } if (ferror(out)) return EXIT_FAILURE; if (fclose(out)) return EXIT_FAILURE; return EXIT_succesS; }

它只需要一个参数,一个文件或设备写入,报告是否写入标准输入,以及读取标准@L_675_1@和错误工作。 (我们通常可以在Bash和POSIX sHell中使用$(tty)来引用实际的终端设备,这样报表就可以在终端上看到了。)用eg编译这个

gcc -Wall -O2 report.c -o report

现在,我们可以检查一些设备:

./run /dev/null ./report $(tty) ./run /dev/zero ./report $(tty) ./run /dev/urandom ./report $(tty)

或任何我们想要的。 在我的机器上,当我在一个文件上运行这个,说

./run some-file ./report $(tty)

写入标准输入,并从标准@L_675_1@和标准错误读取所有工作 – 这是预期的,因为文件描述符指的是相同的,可读和可写的文件描述。

玩完这个之后,结论就是这里根本没有什么奇怪的行为@H_419_46@ 。 如果进程使用的文件描述符只是对操作系统内部文件描述的引用,并且标准输入,@L_675_1@和错误描述符是相互重复的,则它们的行为与期望的完全相同。

我想这是因为在我的Linux中,认情况下, 0和1都是通过读/写打开/dev/tty进程的控制终端。 所以甚至可以从stdout 读取 。

然而,只要你把东西输入或@L_675_1@,

#include <unistd.h> #include <errno.h> #include <stdio.h> int main() { errno = 0; write(0,"Hello World!n",14); perror("write"); }

和运行

% ./a.out Hello World! write: success % echo | ./a.out write: Bad file descriptor

termios函数总是在实际的底层终端对象上工作,所以只要打开一个tty, 0或1是否被使用并不重要。

大佬总结

以上是大佬教程为你收集整理的在STDOUT和STDIN的文件描述符上执行库函数的奇怪行为全部内容,希望文章能够帮你解决在STDOUT和STDIN的文件描述符上执行库函数的奇怪行为所遇到的程序开发问题。

如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。
标签: