GCC简介

不同系统有不同的路径

 
ubantu 
ll /usr/lib/gcc/x86_64-linux-gnu/12/include/stdarg.h 

linux
ll /usr/lib/gcc/x86_64-redhat-Linux/4.8.2/include/stdargs.h
    

编译器

 
https://www.gnu.org/
https://www.gnu.org/software/gcc/releases.html

GCC(GNU Compiler Collection,GNU编译器套件),是由 GNU 开发的编程语言编译器。

“GNU”是“GNU's Not Unix!”(GNU并非Unix!)的首字母递归缩写。 [1]
GNU是一个操作系统,其内容软件完全以GPL方式发布。
其中主要的操作系统是Linux的发行版。
Linux操作系统包涵了Linux内核与其他自由软件项目中的GNU组件和软件,可以被称为GNU/Linux(见GNU/Linux命名争议)。

UNIX是一种广泛使用的商业操作系统的名称。
由于GNU将要实现UNIX系统的接口标准......

编译流程

 
gcc编译器将c源文件到生成一个可执行程序,一共有四个步骤:预处理、编译、汇编和链接
gcc test.c -o test 生成可执行文件的过程:

预处理:
gcc -E test.c -o test.i 将头文件展开、宏替换、去除注释

编译:
gcc -S test.i -o test.s 将C文件编译成汇编文件

汇编:
gcc -c test.s -o test.o 将汇编文件转成二进制文件

链接:
gcc test.o -o test 将函数库中相应的代码组合到目标文件中

几个常见命令及GCC的终极目的

 
gcc a.c 生成一个a.out的可执行文件 
gcc a.c -c 生成一个a.o的中间文件 
gcc -shared -fPIC b.c -o b.so 生成一个b.so共享链接库

 
我们使用gcc,就是为了将一堆c文件变成一个可执行文件... 
我们想要的,只是这个最终的 可执行文件! 
一堆又一堆的参数,详细控制着这个变化的过程    

gcc常用参数

 
-v 查看gcc版本号, 使用--version也可,注意有两个-
-E 生成预处理文件
-S 生成汇编文件
-c 只编译,生成.o文件,通常称为目标文件
-I 指定头文件所在的路径
-L 指定库文件所在的路径
-l 指定库的名字
-o 指定生成的目标文件的名字
-g 包含调试信息,使用gdb调试需要添加-g参数
-On(n=0∼3) 编译优化,n越大优化得越多,比如一些不用的变量不会定义等
-Wall 提示更多警告信息
-D 编译时定义宏,如test.c中用到MAXN但未定义可用gcc -o test test.c -D MAXN=10进行编译

 
-fpic         # flag to set position independent code 
-fno-builtin  # don't recognize build in functions ... 

以-g,-f,-m,-O,-W或--param开头的选项会自动传递给gcc调用的各个子进程.
为了将其他选项传递给这些进程,必须使用-W选项.

标志是,如果通过“指定的-fFLAG”通过和关“ -fno-FLAG”



'f' 定义了“控制代码生成中使用的接口约定”
源代码: https ://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html(例如 -fpci,设置此标志时)

“m”代表模式。一个普遍特征是它有时具有参数。例如
-mabi=name   #abi mode = name
-mcpu=cpu

源代码:https : //gcc.gnu.org/onlinedocs/gccint/Standard-Names.html (例如......当这种模式......)

优化选项

 
gcc -O1 test.c -o test
使用编译优化级别1编译程序。级别为1~3,级别越大优化效果越好,但编译时间越长
    

 
参考
https://blog.csdn.net/weixin_39258979/article/details/101023337


C 头文件

头文件(Head Files)

 
gcc [-Idir...] [-Ldir...]

-I 指定的是头文件目录,head files,源码文件, 通常以.h为后缀
-L 指定的是库文件目录,library files,经过编译的库文件,共享库以.so为后缀

    
在C语言和其他语言中,头文件声明了系统函数和库函数,并且定义了一些常量。

对于C语言,头文件基本上散落于 /usr/include和它的子文件夹下。

其他的编程语言的库函数分布在编译器定义的地方,
比如在一些Linux版本中,X Window系统库函数分布在/usr/include/X11,GNU C++的库函数分布在/usr/include/g++。
这些系统库函数的位置对于编译器来说都是“标准位置”,即编译器能够自动搜寻这些位置。

如果想引用位于标准位置之外的头文件,我们需要在调用编译器的时候加上-I标志,来显式的说明头文件所在文件夹。比如,
$ gcc -I/usr/openwin/include hello.c
会告诉编译器除了标准位置外,还要去/usr/openwin/include看看有没有所需的头文件。
详细情况见编译器的使用手册(man gcc)。

 
gcc -I /opt/include/a.h -I /opt/include/b.h 
gcc -I include/a.h 

 
三个变量设置的路径
C_INCLUDE_PATH
CPLUS_INCLUDE_PATH
OBJC_INCLUDE_PATH

 
/usr/include
/usr/local/include 
gcc库文件路径

 
宏(变量,代码块)定义,

外部文件引入本文件 
    

由#开头,位于文件开始部位,在C文件被编译之前做一些预处理工作,
主要有两个功能:
宏(变量,代码块)定义,
外部文件引入本文件 

文件引入
#include <file.h> 
- 表示把系统头文件名为file.h的文件内容引入当前的位置
- 会按默认路径自动搜索

#include "file.h" 
- 表示把 当前目录 头文件名为file.h的文件内容引入当前的位置,
- 如果当前目录没有,再去找编译时由 -l参数指定的文件

编译阶段处理及重复处理

 
宏(变量,代码块)定义,比如下面的常用于避免头文件多次引用被重复定义,定义过就不再定义了
#ifndef WIN
    #define WIN "win"
#endif 

下面的代码用于条件编译,只有满足条件的代码才会被编辑
#ifdef WIN
/*windows 逻辑代码*/
#endif 

代码示例:
#ifndef ACTIVATION_LAYER_H
#define ACTIVATION_LAYER_H

#include "layer.h"
#include "network.h"

void forward_activation_layer(layer l, network net);
void backward_activation_layer(layer l, network net);

#ifdef GPU
void forward_activation_layer_gpu(layer l, network net);
void backward_activation_layer_gpu(layer l, network net);
#endif

#endif

 
这里的define 就是定义一个变量,
只要这个变量被define过,那么ifndef与ifdef就认得这个变量,
可用于分支判断,在编辑阶段生效

C 库函数

库函数(Library Files)

 
库函数就是函数的仓库,它们都经过编译,重用性不错。通常,库函数相互合作,来完成特定的任务。
比如操控屏幕的库函数(cursers和ncursers库函数),数据库读取库函数(dbm库函数)等。
系统调用的标准库函数一般位于/lib以及/usr/lib。C编译器(精确点说,连接器)需要知道库函数的位置。
默认情况下,它只搜索标准C库函数。

库函数文件通常开头字母是lib。后面的部分标示库函数的用途(比如C库函数用c标识, 数学库函数用m标示),
小数点后的后缀表明库函数的类型:
.a 指静态链接库
.so 指动态链接库
去/usr/lib看一下,你会发现,库函数都有动态和静态两个版本。
$ ls /usr/lib
alsa        games       jvm          modprobe.d        sse2
binfmt.d    gcc         jvm-commmon  modules           sysctl.d
cpp         gems        jvm-exports  modules-load.d    systemd
crda        grub        jvm-private  mozilla           tmpfiles.d
cups        java        kbd          NetworkManager    tuned
debug       java-1.5.0  kde3         polkit-1          udev
dracut      java-1.6.0  kde4         python2.7         x86_64-redhat-linux6E
firewalld   java-1.7.0  kdump        rpm               yum-plugins
firmware    java-1.8.0  kernel       sendmail
fontconfig  java-ext    locale       sendmail.postfix

/lib 只是/usr/lib目录的一个软链接
$ ll /lib
lrwxrwxrwx. 1 root root 7 Apr  4 01:42 /lib -> usr/lib

$ ll /usr/lib/gcc/x86_64-redhat-linux/4.8.5
lrwxrwxrwx. 1 root root 5 Apr  4 01:55 /usr/lib/gcc/x86_64-redhat-linux/4.8.5 -> 4.8.2
$ ll /usr/lib/gcc/x86_64-redhat-linux/4.8.2
total 4676
drwxr-xr-x. 2 root root    4096 Apr  4 01:55 32
-rw-r--r--. 1 root root    2824 Oct 30  2018 crtbegin.o
-rw-r--r--. 1 root root    3072 Oct 30  2018 crtbeginS.o
-rw-r--r--. 1 root root    3272 Oct 30  2018 crtbeginT.o
-rw-r--r--. 1 root root    1360 Oct 30  2018 crtend.o
-rw-r--r--. 1 root root    1360 Oct 30  2018 crtendS.o
-rw-r--r--. 1 root root    3968 Oct 30  2018 crtfastmath.o
-rw-r--r--. 1 root root    3568 Oct 30  2018 crtprec32.o
-rw-r--r--. 1 root root    3576 Oct 30  2018 crtprec64.o
-rw-r--r--. 1 root root    3568 Oct 30  2018 crtprec80.o
drwxr-xr-x. 2 root root      86 Apr  4 01:55 finclude
drwxr-xr-x. 2 root root    4096 Apr  4 01:46 include
-rw-r--r--. 1 root root    6904 Oct 30  2018 libasan_preinit.o
-rw-r--r--. 1 root root      38 Oct 30  2018 libasan.so
-rw-r--r--. 1 root root      40 Oct 30  2018 libatomic.so
-rw-r--r--. 1 root root    6860 Oct 30  2018 libcaf_single.a
-rwxr-xr-x. 1 root root 1268416 Oct 30  2018 libcloog-isl.so.4


$ ll /usr/lib/gcc/x86_64-redhat-linux/4.8.2/include/
total 584
-rw-r--r--. 1 root root  1801 Oct 30  2018 adxintrin.h
-rw-r--r--. 1 root root  3082 Oct 30  2018 ammintrin.h
-rw-r--r--. 1 root root 57493 Oct 30  2018 avx2intrin.h
-rw-r--r--. 1 root root 48008 Oct 30  2018 avxintrin.h

linux 动态库

 

    

A shared library

 
A shared library (also known as a shared object, or as a dynamically linked library) 
is similar to a archive in that it is a grouping of object files. 

However, there are many important differences.
The most fundamental difference is that when a shared library is linked into a program, 
the final executable does not actually contain the code that is present in the shared library.

Instead, the executable merely contains a reference to the shared library. 
If several programs on the system are linked against the same shared library, 
they will all reference the library, but none will actually be included.
Thus, the library is “shared” among all the programs that link with it.

A second important difference is that a shared library is not merely a collection of object files, 
out of which the linker chooses those that are needed to satisfy undefined references. 
Instead, the object files that compose the shared library are combined into a single object file 
so that a program that links against a shared library always includes all of the code in the library, 
rather than just those portions that are needed.

To create a shared library, you must compile the objects that will make up 
the library using the -fPIC option to the compiler, like this:
% gcc -c -fPIC test1.c
        
The -fPIC option tells the compiler that you are going to be using test.o as part of a shared object.
                                                                    ---摘自《Advanced Linux Programming》

动态库就是 多个库文件的集合

 
linux操作系统中,
1.和静态库类似,动态库文件也是一些目标文件的集合体。
2.动态库的后缀名是.so,对应于windows操作系统的后缀名为.dll的动态库。
3.可以使用gcc命令来创建一个动态库文件。

与静态库将涉及的文件都包括到最终可执行文件中不同,
动态库只告诉最终可执行文件所引用内容的位置,
不会写到最终的可执行文件中 

共享链接库(Shared Libraries)

 
库函数有静态链接库与动态链接库,动态链接库就是共享链接库

静态链接库的一个缺点是,如果我们同时运行了许多程序,并且它们使用了同一个库函数,这样,在内存中会大量拷贝同一库函数。这样,就会浪费很多珍贵的内存和存储空间。使用了共享链接库的Linux就可以避免这个问题。
共享函数库和静态函数在同一个地方,只是后缀有所不同。比如,在一个典型的Linux系统,标准的共享数序函数库是/usr/lib/libm.so。
当一个程序使用共享函数库时,在连接阶段并不把函数代码连接进来,而只是链接函数的一个引用。

当最终的函数导入内存开始真正执行时,函数引用被解析,共享函数库的代码才真正导入到内存中。

这样,共享链接库的函数就可以被许多程序同时共享,并且只需存储一次就可以了。
共享函数库的另一个优点是,它可以独立更新,与调用它的函数毫不影响。契合了解耦的思想。

自己编写一个动态库示例

 

vim myfunc.c
#include <stdio.h>
int getnum(){
    return 5;
}

gcc -c -fPIC myfunc.c
gcc -shared -fPIC -o libmyfunc.so myfunc.o
mkdir /usr/local/mylib
cp libmyfunc.so /usr/local/mylib/

linux系统动态库默认位置为 /lib and /usr/lib, by default

export LD_LIBRARY_PATH=/usr/local/mylib:$LD_LIBRARY_PATH

vim chk.c
#include <stdio.h> 
int main(){
    extern int getnum();
    printf("return value is %d\n",getnum());
    return 0;
}

gcc -c chk.c 
gcc -o chk chk.o -L. -lmyfunc    #-L查找范围为当前目录 -l动态链接库名
./chk
return value is 5

 
编译目标代码时,gcc -Wl, options 指定,多个路径之间冒号分割

环境变量 LD_LIBRARY_PATH 指定的动态库搜索路径

配置文件 /etc/ld.so.conf 中指定的动态库搜索路径;

默认的动态库搜索路径/lib;

默认的动态库搜索路径/usr/lib。

动态库搜索路径

 
export LD_LIBRARY_PATH=动态库目录 

/*  test.c  */  
int f()   
{  
    return 3;  
}  


gcc -c -fPIC test.c
gcc -shared -fPIC -o libtest.so test.o
会在当前目录下生成一个libtest.so动态库文件

/*  app.c   */  
#include <stdio.h>  
extern int f();  
int main()   
{  
    printf(“return value is %d\n”,f());  
    return 0;  
}  

gcc –c app.c
gcc -o app app.o -L. –ltest
文件在同一个目录(即当前目录)下
但是当我们执行./app命令,来执行这个可执行文件时,却提示如下错误:

 

libtest.so文件明明就在当前目录下,为什么会提示找不到呢? 继续看书,书上有这样的话:
    When you link a program with a shared library, the linker does not put the full path to the shared library in the resulting executable. 
Instead, it places only the name of the shared library.When the program is actually run, 
the system searches for the shared library and loads it.The system searches only /lib and /usr/lib, by default.
If a shared library that is linked into your program is installed outside those directories, it will not be found, and the system will refuse to run the program.
原来linux和windows的机制是不同的,它不会在当前目录下寻找动态连接库文件,它只会在标准路径下寻找。
(The system searches only /lib and /usr/lib, by default.)。

 
我们可以使用一个命令,使得操作系统去我们指定的路径下面去寻找。
假设libtest.so文件所在的目录是/root/Desktop/aabb,那么执行命令
export LD_LIBRARY_PATH=/root/Desktop/aabb
后,再执行./app,我们发现,程序就正常运行了。

静态库搜索路径

 
搜索 -I 指定的目录
搜索 gcc 的环境变量 LIBRARY_PATH 对应的目录

默认系统目录
/lib
/usr/lib
/usr/local/lib


make与makefile

make及makefile是什么

 
make是C中专用于编译连接的一个管理命令
makefile是写有一系列make规则的脚本文本,它完成项目的编辑安装工作

 
在命令行中执行make,OS会自动去执行命令所在目录下的makefile中的逻辑 

makefile示例

 
Makefile如下:
all:a b 
a:src/a.c 
    gcc -o out/a src/a.c 
b:src/b.c 
    gcc -o out/b src/b.c 

clean:
    rm -rf out/{a,b} 

test_make目录结构如下:
out 
src
  - a.c 
  - b.c 
Makefile  

执行记录:
(base) xt@ai1:/opt/tpf/cwks/src/test_make$ make
gcc -o out/a src/a.c 
gcc -o out/b src/b.c 

查看out目录下文件:
(base) xt@ai1:/opt/tpf/cwks/src/test_make$ cd out
(base) xt@ai1:/opt/tpf/cwks/src/test_make/out$ ls
a  b

make clean:
(base) xt@ai1:/opt/tpf/cwks/src/test_make/out$ cd ..
(base) xt@ai1:/opt/tpf/cwks/src/test_make$ make clean
rm -rf out/{a,b}

makefile中的变量

 
DEBUG=0
OPTS=-Ofast
ifeq ($(DEBUG), 1) 
OPTS=-O0 -g
endif

DEBUG为变量,其值为0
ifeq表示如果相等
$(DEBUG)表示取DEBUG变量的值

C 调用shell

system代码示例

 
#include <stdlib.h> 

int main(){
    system("pwd");
    return 0;
}

 
include默认指/usr/include目录下的.h文件,
stdio.h,stdlib.h就在该目录下  
$ ll /usr/include/std*.h
-rw-r--r--. 1 root root  1629 Oct 30  2018 /usr/include/stdc-predef.h
-rw-r--r--. 1 root root  8135 Oct 30  2018 /usr/include/stdint.h
-rw-r--r--. 1 root root  2806 Oct 30  2018 /usr/include/stdio_ext.h
-rw-r--r--. 1 root root 31641 Oct 30  2018 /usr/include/stdio.h
-rw-r--r--. 1 root root 34030 Oct 30  2018 /usr/include/stdlib.h

system(执行shell 命令)

 
相关函数 fork,execve,waitpid,popen

表头文件 #include <stdlib.h> 

定义函数 int system(const char * string);

函数说明 system()会调用fork()产生子进程,由子进程来调用/bin/sh-c 
来执行参数string字符串所代表的命令,此命令执行完后随即返回原调用的进程。
在调用system()期间 SIGCHLD 信号会被暂时搁置,SIGINT和SIGQUIT 信号则会被忽略。

返回值 
如果system()在调用/bin/sh时失败则返回127,其他失败原因返回-1。

若参数string为空指针(NULL),则返回非零值。
如果system()调用成功则最后会返回执行shell命令后的返回值,但是此返回值也有可能为system()调用/bin/sh失败所返回的127,
因此最好能再检查errno 来确认执行成功。

附加说明 
在编写具有SUID/SGID权限的程序时请勿使用system(),system()会继承环境变量,通过环境变量可能会造成系统安全的问题。

还返回值的system:将输出结果保存到临时文件

 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define CMD_STR_LEN 1024

char* readAll(const char* filename) {
    char* data;
    FILE* fp = fopen(filename, "r");
    fseek(fp, 0, SEEK_END);
    int num_int = (int)ftell(fp);
    data = (char*)malloc(num_int * sizeof(char));
    memset(data, 0, num_int);
    fseek(fp, 0, SEEK_SET);
    int size_int = sizeof(int);
    fread(data, size_int, num_int, fp);
    fclose(fp);
    return data;
}

char* mysystem(const char* cmd){
    int fd;
    char* res;
    char tmpfile[]="/tmp/tmp_XXXXXX";
    if((fd=mkstemp(tmpfile))==-1)
    {
        printf("Creat temp file faile./n");
    }else{
        unlink(tmpfile);

        //------------命令执行 开始---------------- 
        char cmd_string[CMD_STR_LEN];
        sprintf(cmd_string, "%s > %s", cmd, tmpfile);
        system(cmd_string);
        //------------命令执行 结束---------------- 

        res = readAll(tmpfile);
        close(fd);
    }
    
    return res;
}

int main() {
    char* cmd = "cat /tmp/a.log";
    char* res = mysystem(cmd);
    printf("%s", res);
    return 0;
}        

 

    

popen(建立管道I/O)

 
#include <stdlib.h> 
int main() 
{ 
    FILE * fp; 
    int ch; 
    char buffer[800]; 
    fp=popen("df -h","r"); 

    //读取1行或指定字符
    fgets(buffer,sizeof(buffer),fp); 
    printf("%s",buffer); 

    //一个字符一个字符地读取剩下的所有字符,并输出到屏幕,
    while ((ch = fgetc(fp)) != EOF){
            putchar(ch);
    }

    //pclose用于关于popen打开的子进程,并返回其返回值 
    pclose(fp); 
    return 0;
}
        

 
相关函数 pipe,mkfifo,pclose,fork,system,fopen

表头文件 #include <stdio.h> 

定义函数 FILE * popen( const char * command,const char * type);

函数说明 popen()会调用fork()产生子进程,然后从子进程中调用/bin/sh -c 来执行参数command的指令。
参数type可使用“r”代表读取,“w”代表写入。
依照此type值,popen()会建立管道连到子进程的标准输出设备或标准输入设备,然后返回一个文件指针。
随后进程便可利用此文件指针来读取子进程的输出设备或是写入到子进程的标准输入设备中。

此外,所有使用文件指针(FILE*)操作的函数也都可以使用,除了fclose()以外。

返回值   若成功则返回文件指针,否则返回NULL,错误原因存于errno中。
错误代码 EINVAL参数type不合法。
注意事项 在编写具SUID/SGID权限的程序时请尽量避免使用popen(),popen()会继承环境变量,通过环境变量可能会造成系统安全的问题。

 

    

使用vfork()新建子进程,然后调用exec函数族

 
#include <unistd.h> 

int main()
{
    char* argv[] = {"cat", "/tmp/a.log"};
    if(vfork() == 0){
        execv("/usr/bin/cat", argv);
    }
    return 0; 
}

 


 

  

 


系统IO与标准IO

 
IO 指 input/output - 输入输出
文件IO, 对文件输入/输出,  将数据写入到文件/从文件中将数据读取出来

访问文件方式有两种:
系统IO,  系统调用
标准IO,  库调用,供用户使用,通常是对系统IO的封装,目的在于方便使用/提高性能 

STDOUT_FILENO

 
$ less /usr/include/unistd.h
/* Standard file descriptors.  */
#define STDIN_FILENO    0       /* Standard input.  */
#define STDOUT_FILENO   1       /* Standard output.  */
#define STDERR_FILENO   2       /* Standard error output.  */

STDOUT_FILENO定义于/usr/include/unistd.h头文件中,unix标准库文件
STDOUT_FILENO 从名字上拆开来读就是 std out file no ,标准输出文件号,是1,1号文件
0号文件是STDIN_FILENO
2号文件是STDERR_FILENO 

使用0,1,2这三个数字代表/表示三个流的接口,
操作系统启动,系统内核加载到内存之后,这个内核与外部用户区信息的交互就要用到流,
最最基本就这三个,0-标准输入流,1-标准输出流,2-标准错误输出
shell命令中的 2>&1 意思就是将错误输出重定位到标准输出中 

stdin stdout

 
$ less stdio.h
/* Standard streams.  */
extern struct _IO_FILE *stdin;          /* Standard input stream.  */
extern struct _IO_FILE *stdout;         /* Standard output stream.  */
extern struct _IO_FILE *stderr;         /* Standard error output stream.  */
/* C89/C99 say they're macros.  Make them happy.  */
#define stdin stdin
#define stdout stdout
#define stderr stderr

可以看到stdin,stdout,stderr是三个_IO_FILE的指针,即IO文件指针类型,通常说成FILE* 
这三个是用户区的标准API,与内核区的0-STDIN_FILENO,1-STDOUT_FILENO,2-STDIN_FILENO一一对应 
stdin,stdout,stderr在操作系统启动时,也是自动打开的

stdin默认从键盘输入
stdout默认输出到屏幕
stderr默认输出到屏幕
按按键盘,屏幕上就能看到按的内容,后台stdin接收键盘的信息,stdout将之输出到屏幕上
stdin,stdout 建立了 键盘到屏幕的一条 流 通道,中间缓冲数据的地方是内存的缓冲区

stdout 带有缓冲区,遇到换行符或者程序结束输出到屏幕;
stderr 不带有缓存区,直接输出到屏幕。

关闭流stdout后就屏幕上就没有输出了

 
#include <stdio.h> 

int main() {
    printf("aaa\n");
    fclose(stdout);
    printf("bb\n");
    return 0;
}

$ gcc e.c 
$ ./a.out 
aaa
    
stdin,stdout是文件指针FILE*类型,
可以对其调用fcolse,fread等f文件操作相关的方法

文件缓冲与非文件缓冲

文件缓冲

 
读缓冲:数据从磁盘读取到内存的 缓冲区,再转到其他地方,比如屏幕输出
写缓冲:数据写入磁盘前,先写入到内存的缓冲区,再从缓冲按一定规则刷入磁盘
对应API:fopen, fclose, fread, fwrite, fgetc, fgets, fputc, fputs, freopen, fseek, ftell, rewind

优点:
缓冲的优势就是 批量写比单写减少了外部设备读写的次数,
减少了用户区与内核区的交互次数,适合连续内存地址的操作
缺点:
但若写的比较零散,落盘时仍然要分散去写,缓冲一下反而是多此一举;
另外,若是顺序与随机对磁盘来说区别不大,缓冲也是个负担

非文件缓冲

 
就是没有缓冲这一步了,只能写二进制文件,对应的API有
open, close, read, write, getc, getchar, putc, putchar

前面带f的是标准IO,这里不带f的是系统IO,fopen转到内核后,调用的还是open 

参考
    linux: c语言 关闭标准输出STDOUT_FILENO对父子进程的影响