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语言编程的相关知识如下:
- 编译的时候加上-Wall参数,让编译器输出所有的警告信息,作为严谨的程序员,我们应该排除出现的所有的警告信息;
- 在编写程序的时候,务必加上正确的头文件,如不太清楚该函数属于哪个头文件,可借助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方便大家记忆
如上述分析有错误之处,还望各位不吝指出,谢谢!