由stdout引发的思考

简介: 由stdout引发的思考

1 前言

还是继续以前的风格吧,喜欢在osc的讨论区看一些问题,然后选择一些有意思的加以深入分析和调研,最后将分析的成果整理成博文和大家一起分享,当然本次博文当然也不例外。

今天早些时候在osc的技术问答模块发现一个有意思的问题http://www.oschina.net/question/1020892_114830,截图如下:

现将相关的分析思路整理成博文,和大家一起探讨下。

2 分析

以下是该问题的分析思路,如有错误之处,欢迎大家不吝指出,谢谢!

【1】man 3 stdout我们得到以下部分输出:

#include <stdio.h>
       extern FILE *stdin;       extern FILE *stdout;       extern FILE *stderr;

从上面我们可以看到stdout的类型是FILE *


【2】man 3 write我们得到以下部分输出:

#include <unistd.h>
ssize_t pwrite(int fildes, const void *buf, size_t nbyte,off_t offset);        
ssize_t write(int fildes, const void *buf, size_t nbyte);

从上面我们可以看到write(...)函数的头文件为unistd.h,且第一个参数的类型为int

【3】楼主帖子中的第一种方式

#include<stdio.h>int main(){
    write(stdout,"hahhh",6);      
    return 0;
}

结合【1】中stdout的类型为FILE *以及【2】中的write(...)的第一个参数为int,所以在实际运行的时候,会将FILE *类型强制转换为int,而(int)stdout的结果为:-1122261984(不同的机器结果可能不一样)

write(stdout,"hahhh",6);

等同于:

write(-1122261984,"hahhh",6);

且我们知道STDOUT_FILENO为1,所以我们在屏幕上看不到任何的输出;

另外,如果我们加上-Wall参数进行编译,会得到如下的警告信息:

warning: passing argument 1 of ‘write’ makes integer from pointer without a cast [enabled by default]      write(stdout, "hahhh", 6);


这条警告的大致意思是write函数的第一个参数直接将指针赋值给整数;

【4】楼主帖子中的第二种方式:

#include<stdio.h>int main(){
    write(1,"hahhh",6);     
    return 0;
}

当我们用gcc命令并加上-Wall编译后得到下面的警告信息:

warning: implicit declaration of function ‘write’ [-Wimplicit-function-declaration]
      write(stdout, "hahhh", 6);
      ^


出现这个警告的原因是从【2】我们看到write(...)的头文件是<unistd.h>,所以务必加上#include <unistd.h>

另外1为STDOUT_FILENO即标准输出,所以在屏幕上看到了我们所期望的输出。

Q:相信大家看到这里的时候,会发现一个问题,那就是为什么上面的代码中没有加<unistd.h>这个头文件,我们的程序依然可以运行,惟独只有编译的时候的跳出的一个警告信息。

我们先来了解一下基本的链接知识:

当gcc编译*.c文件的时候,如果发现某函数(如test())未定义或声明,编译器并未报错而只是提出警告,警告的信息一般如下:

warning: implicit declaration of function ‘test’ [-Wimplicit-function-declaration]

编译器默认该函数会在其他的模块中定义,在链接阶段,如果还未找到该函数定义则报链接错误,一般错误信息如下:

undefined reference to `test'
  collect2: error: ld returned 1 exit status

所以针对上面提出的问题,源程序中即使没有包含<unistd.h>头文件,同样可以编译通过。那为什么可以链接成功呢?

我们运行ldd a.out可以看到a.out依赖的动态链接库如下所示:

linux-vdso.so.1 =>  (0x00007fff1d7fe000)
libc.so.6 => /lib64/libc.so.6 (0x00000034bce00000)
/lib64/ld-linux-x86-64.so.2 (0x00000034bc600000)


从上面输出的结果,我们看到了大名鼎鼎的libc.so.6即libc动态链接库,我们从wikipedia中得到glic如下描述:

glibc provides the functionality required by the Single UNIX Specification, POSIX (1c, 1d, and 1j) and some of the functionality required by ISO C99, Berkeley Unix (BSD) interfaces, the System V Interface Definition (SVID) and the X/Open Portability Guide (XPG), Issue 4.2, with all extensions common to XSI (X/Open System Interface) compliant systems along with all X/Open UNIX extensions. In addition, glibc also provides extensions that have been deemed useful or necessary while developing GNU.

另外我们得到unistd.h的相关描述如下:

In the C and C++ programming languages, unistd.h is the name of the header file that provides access to the POSIX operating system API.


从上面的描述,我们可以看到glibc实现了single unix specificaton, POSIX等功能,且我们了解到unistd.h是POSIX API的头文件。

至此,我们得到libc中实现了<unistd.h>中定义的所有函数当然包括我们程序中用到的write(...)函数,所以链接过程不会出错,程序能够正常运行。

总结

从上述对帖子问题的分析,我们总结c语言编程的相关知识如下:

  1. 编译的时候加上-Wall参数,让编译器输出所有的警告信息,作为严谨的程序员,我们应该排除出现的所有的警告信息;
  2. 在编写程序的时候,务必加上正确的头文件,如不太清楚该函数属于哪个头文件,可借助linux下强大的man命令,另外还可以看到关于该函数的详细说明;

注1:

man 命令的不同section的具体含义如下:

Section Description
1 General commands
2 System calls
3 Library functions, covering in particular the C standard library
4 Special files (usually devices, those found in /dev) and drivers
5 File formats and conventions
6 Games and screensavers
7 Miscellanea
8 System administration commands and daemons

man 3为直接查看c标准

注2:

unistd.h中的unistd表示unix standard方便大家记忆

如上述分析有错误之处,还望各位不吝指出,谢谢!

引用

[1] http://www.gnu.org/software/libc/

[2] http://en.wikipedia.org/wiki/GNU_C_Library

目录
相关文章
|
3月前
|
Java API 开发者
你的应用是不是只有service_stdout.log?
本文记录了logback-spring.xml文件不生效问题的整体排查思路。
|
6月前
|
Shell
在Shell中,您可以同时重定向标准输出(STDOUT)和错误输出(STDERR)
在Shell中,您可以同时重定向标准输出(STDOUT)和错误输出(STDERR)
462 1
|
Python
Python中标准输入(stdin)、标准输出(stdout)、标准错误(stdout)的用法
Python中标准输入(stdin)、标准输出(stdout)、标准错误(stdout)的用法
149 0
|
编译器 C++
文件操作以及相关的函数,fputc,fgetc,fputs,fgtes,fprintf,fscanf,sprintf,sscanf
🐰文件操作 🌸打开文件 🏡文件的顺序读写 🌸fputc字符输入函数(适合所有输入流) 🌸fgetc(适合所有的输出流) 🌸fputs(适合所有的输入流) 🌸fgets(适合所有的输出流) 🏡格式化的读写 🌸fprintf(适合所有的输入流) 🌸fscanf读取文件(适合所有的输出流) 🏡流 🌸屏幕这个流(stdout)输出 🏡对比:printf/fprintf/sprintf和scanf/fscanf/sscanf 🌸sprintf(把格式化数据转换成字符串) 🌸sscanf(把字符串转换成相应格式化数据)
文件的介绍,流的概念,FILE*指针函数 fgetc fputc fgetcs fputs fscanf fprintf的使用实例及说明
文件的介绍,流的概念,FILE*指针函数 fgetc fputc fgetcs fputs fscanf fprintf的使用实例及说明
115 0
|
Linux 存储
Linux 调用系统命令并截获标准输出(stdout)和错误输出(stderr)
<pre><b>char</b> ret[1024]; <b>char</b> *<b>DoSysCmd</b>(<b>char</b> * cmdline){ <b>FILE</b> *fp; <b>char</b> line[32]; <b>char</b> cmdtmp[256]; <b>memset</b>(cmdtmp,0x00,256);
1771 0
|
Java
日志输出之log4j配置
整理一下log4j.properties配置文件的相关内容,留备后用。
211 0
|
Python