C 中 关于printf 函数高度剖析

  用VS2015 建一个 空的控制台.如下

此间说一下三个涵盖条件 是 C编写翻译器对于可变参数函数 必须(私下认可) 是
__cdecl 修饰的,详细的少数诠释如下:

// 定义 char* 类型,这个类型指针偏移量值为 1,
// 例如
// char *a = NULL ; 此时 a地址是 0x0
// ++a; => 此时 a地址为 0x0 + 1*1 = 0x1位置处
        typedef char* va_list;


//
// 定义获取变量地址的宏
//
#define _ADDRESSOF(v) (&(v))

最后解锁 再次回到结果.

#include <unistd.h>

// 函数说明:pause()会令目前的进程暂停(进入睡眠状态),直至信号(signal)所中断。
// 返回值:只返回-1
int  pause(void);

后记

新兴在 printf 源码中找见了

C模板技术

 

思路是先清空输入流stdin
,再用getchar等待函数,等待用户输入回车甘休此次决定台学习.

1. C底层库源码 Window和Linux

图片 1

下边包车型客车宏就简单了

3.printf函数的源码完结

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

//简单的日志宏 fmt必须是字面量字符串
#define ERRLOG(fmt,...) \
        fprintf(stderr,"[%s:%s:%d]" fmt "\r\n",__FILE__,__FUNCTION__,__LINE__,##__VA_ARGS__)

//简单系统等待函数
#define _STR_PAUSE "请按任意键继续. . ."
#define SPAUSE() \
    rewind(stdin),printf(_STR_PAUSE),getchar()

//
// 需要一个机器扫描 输入的字符串,输入的字符串个数是不确定的.并从中找出 长度 小于 5的 字符串, 输出 索引和当前串的内容
// 
#define _INT_FZ (5)
//
// 这里 最后一个参数 必须是 NULL,同 linux中execl函数簇参数要求
// sstr : 开始的串
//
void with_stdin(const char *sstr, ...);

int main(int argc, char *argv[])
{
    with_stdin(NULL);
    with_stdin("1","1234331","adfada","ds",NULL);
    with_stdin("a","ad","adf","asdfg","asdsdfdf","123131",NULL);
    with_stdin("1","3353432", "1234331", "adfada", "ds","dasafadfa","dasdas", NULL);

    SPAUSE();//等待函数
    return 0;
}

void 
with_stdin(const char *sstr, ...)
{
    static int __id; // 第一声明的时候赋值为0,理解成单例
    va_list ap;
    const char *tmp;

    if (NULL == sstr) {
        ERRLOG("Warning check NULL == sstr.");
        return;
    }

    if (_INT_FZ > strlen(sstr))
        printf("%d %s\n",__id,sstr);
    ++__id;

    va_start(ap, sstr);
    while ((tmp = va_arg(ap, const char*)) != NULL) {
        if (_INT_FZ > strlen(tmp))
            printf("%d %s\n", __id, tmp);
        ++__id;
    }

    va_end(ap);
}

2.printf 函数可重入商讨

而这几个宏正是为着找到任何参数而设计的.宗旨是基于变量的内部存款和储蓄器布局,指针来回指.依次剖析如下:

大家先举个例证,假设今后有诸如此类二个须要”须求一个不定参数整型求和函数”.

此处配置的是x86 开发环境文件多,配置x64文件就很少了. 这一个学会了 现在 就特别简单了.

第1 测试 代码如下
,须求同学团结敲三遍,关于pthread的代码 照旧相比较复杂,当然正是大家付出库用到的大概是它中下难度部分api.

调用者清除,称为手动清栈
=> 在 b 汇编代码中 会插入 清空a函数栈的汇编代码

本条职能在 Linux 有个 系统函数如下

肯定有错的,例如错别字,技术错误等等,欢迎沟通指正,下次右机会享受pthread 开发专题.最终前面分享多少个 本文参考的东西

在品种右击采用属性,大概 键盘右击键 + 昂Cora

落到实处就用了

__cdecl 是C
Declaration的缩写(declaration,声明),

 详细一点的测试代码如下

 

有个别时候 需求在八个平台,下 完毕等待函数
,就须要通过宏来判断,那是很恶心的.恐怕是私有认为,可移植程序内部都以黑心丑陋的 腐尸堆积体.

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

// 需要一个不定参数整型求和函数,len表示参数个数
int sum_add(int len, ...);



int main(int argc, char *argv[])
{    
    int sum;

    sum = sum_add(1, 2);
    printf("sum = %d\n",sum);

    sum = sum_add(4,1,2,3,4);
    printf("sum = %d\n", sum);

    sum = sum_add(10, 1, 2, 3, 4,5,6,7,8,9,10);
    printf("sum = %d\n", sum);

    system("pause");
    return 0;
}
// stdarg.h
...
#define va_start __crt_va_start
#define va_arg   __crt_va_arg
#define va_end   __crt_va_end
#define va_copy(destination, source) ((destination) = (source))
...

//vadefs.h
...
typedef char* va_list;
...   
#define _ADDRESSOF(v) (&(v))
...
#elif defined _M_IX86

    #define _INTSIZEOF(n)          ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))

    #define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))
    #define __crt_va_arg(ap, t)     (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
    #define __crt_va_end(ap)        ((void)(ap = (va_list)0))

#elif defined _M_ARM
....

#define __crt_va_start(ap, x) __crt_va_start_a(ap, x)
...

  
http://sourceware.org/pthreads-win32/

它形成的功用相当于三个大约的 代码解析器. 总共达成代码2千多行. 看看觉得 Linux内核确实相比较屌,单单那些vprintf.

图片 2

首先步 去官网上下载源码包

 

巨集名称
描述
相容
va_start
使va_list指向起始的参数
C89
va_arg
检索参数
C89
va_end
释放va_list
C89
va_copy
拷贝va_list的内容
C99

穷人没有选用,有的是生活和挣扎.长这么大才了然初级中学生物老师说的,物竞天择适者生存,呵呵大合唱.

图片 3

stdarg.h数据类型

做到相应的效益,在做到经过中,对流举行控制,该保留的保存,该出口输入,改扩大体量的扩大体量,通过文件锁锁住 流输入输出.

 

首先看摘录的源码,那里先分析Window 上源码,Linux上也一样.其实Linux源码更易于看,因为它简洁高效.都一般,重点看个人抉择.

还有二个编写翻译器Pelles C对C99帮助的最好,对C11支撑的仍是能够.有机会我们能够玩玩.做为小白 还盼望C11拓宽开来.

  /* Lock stream.  */
  _IO_cleanup_region_start ((void (*) (void *)) &_IO_funlockfile, s);
  _IO_flockfile (s);

正文

图片 4

末尾是那般的

第二步 建一个C控制台

 

 

参数从右向左入栈 =>
最后三个参数先入栈,最终第2个参数在栈顶

一次解说

那边再扯一点,最近用的C标准最多是C89,流行编译器例如gcc,VS二〇一五大概都帮忙,C89和C99.

再来分析 地址偏移宏

其三步  在控制巴尔的摩添加 一些文书

// ap 是va_list 声明变量,第一次时候调用
// v 表示 可变函数中第一个参数 
// 执行完毕后 ap指向 v 变量后面的下一个 函数参数
 #define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))

// t 只能是类型,int char double ....
// 操作之后 ap又指向下一个函数参数,但是返回当前ap指向的位置处
// 讲完了,关键看自己多写,多读源码.有些大神都是不看注释 直接通过源码就入手框架了
 #define __crt_va_arg(ap, t)     (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))

// 清空ap变量,等同于简单的清空野指针
    #define __crt_va_end(ap)        ((void)(ap = (va_list)0))

#define va_start __crt_va_start
#define va_arg   __crt_va_arg
#define va_end   __crt_va_end

// 地址赋值 , 直接等于 主要用于 ap_two = ap_one
// 具体 写法就是 va_copy(ap_two,va_one) , 目前基本是冷板凳
#define va_copy(destination, source) ((destination) = (source))

图片 5

第④步:修改头文件 去掉争持

到那边环境就配备好了. 上面 直接切入主题. 

 先添加这么些头文件 shift + alt
+ A,将 七个头文件添加到项目里来,如下:

Linux上是vprintf函数,window上是_output_l函数,以vprintf为例,难题在于 格式语法解析,

亟需丰富到 刚才项目 (右击在文件夹下打开那些地点) 如下图

 

率先 添加 VS撤消安全监测宏 _CRT_SECURE_NO_WARNINGS

内部gcc帮衬的比VS要好.终究VS主打大巴是C夏普和CPlusPlus.

图片 6

 

末尾其它静态库,当找不见了协调添加. 当然借使 你想在 VS 通过代码添加静态库
,代码 如下

图片 7

前边获取fmt之后的参数,并且加锁 调用另3个种类输出函数_output_l

 

是否感到很简短,先不难检查和测试一下

// 添加 静态库 pthreadVC2.lib
// 放在 文件一开始位置处,一般放在头文件中
#pragma comment(lib,"pthreadVC2.lib")

图片 8

大家的作业需要是这么的, 要求三个机械扫描 输入的字符串,输入的字符串个数是不鲜明的.

图片 9

2. posix 四线程程序设计

因为C11标准对一些视角常用模块例如三十二线程,数学复数,新的天水库函数等等,缺点是太丑了.

表示C语言默许的函数调用方法:全体参数从右到左依次入栈,那几个参数由调用者清除,称为手动清栈。

 

底层文件读写,CPU变量优化,宏,指针,共用体漫天飞.但这几个函数 仍是能够搞得.首要思路是环绕 状态表(能够知晓为业务表)

被调用函数不会供给调用者传递多少参数,调用者传递过多照旧过少的参数,甚至完全不一样的参数都不会发出编写翻译阶段的一无可取。

那里运维的结果如下:

将 pthread.h 上面 299行 改成 上边那样,直接在当前目录下找头文件

1.1 可变参数机制介绍

printf函数的源码实现.

int __cdecl printf (
        const char *format,
        ...
        )
/*
 * stdout 'PRINT', 'F'ormatted
 */
{
    va_list arglist;
    int buffing;
    int retval;

    _VALIDATE_RETURN( (format != NULL), EINVAL, -1);

    va_start(arglist, format);

    _lock_str2(1, stdout);
    __try {
        buffing = _stbuf(stdout);

        retval = _output_l(stdout,format,NULL,arglist);

        _ftbuf(buffing, stdout);

    }
    __finally {
        _unlock_str2(1, stdout);
    }

    return(retval);
}

此间扯一点,对于system(“pause”); 是调用系统shell 的pause命令,正是让日前cmd关闭停留一下,输出一段话等待一下. 功效图如下

本条细节会让投机做的日志库轮子快一点.

正是加锁的意思.所以printf 是可重入的函数.说了那般多,其实意思 未来 写文件能够一向拼3个大串直接printf 就足以了.

第六步 添加一些文书包括

 

在315行 回车一下 添加下边宏申明,去掉重复结构定义

第1大家需求搭建2个pthread 开发条件在 Window上,假若您是用Linux,稍微新一点的系统,未来都以私下认可pthread线程库.上边 笔者就讲解 pthread 如何搭建.

 那里同样作者也以window 为例 . 具体见上边代码

 自个儿多点点点,下载最新版的此时此刻是
2-9-1,好久没更新了,在window上行使,还有点麻烦,必要简单的改动力源代码.

 

// 需要一个不定参数整型求和函数
int 
sum_add(int len, ...)
{
    int sum = 0;
    va_list ap; 

    va_start(ap, len); // 初始化 将ap参数指向 len 下一个参数位置处
    while (len > 0) {
        int tmp = va_arg(ap, int); // 获取当前参数,并且将ap指向 下一个参数位置处
        sum += tmp;
        --len;
    }
    va_end(ap); // 清除(销毁)ap变量

    return sum;
}

图片 10

下边继续回到 可变参数的话题上. 其实明白 上边 代码,主如若领略那1个宏是什么意思.

 

动静表机制

当然还有变化的 log.txt 文件,

嘿嘿,其实 printf函数 源码 真的很简短,只要精通了 可变参数机制读上边代码很简单.它的复杂见另三个函数.

#define HAVE_STRUCT_TIMESPEC

stdarg.h宏

2.1 printf 函数测试

切切实实贯彻代码如下

 

调用者,被调用函数 => b() { a();} ,
a是被调用函数,b是调用者函数

  必要加上的文书如下:

#include "sched.h"

题外话 

      这篇博文首要围绕printf函数分析的,首要教学printf 使用C的可变参数机制, printf是还是不是可重入(是还是不是线程安全),

实则某些时候 技术 依旧有点难的, 越来越多国同行喜欢不是技巧,而是 可以增强 人命币的 手段,顺带做一件其它事.

末尾添加静态库

1.2 通过三个例子将可变参数机制结尾

下边介绍1个 本人写的叁个通用函数  ,通用控制台学习的等待函数.

到此地C可变函数机制的源码分析完结.

从而那种老标准都有惊无险,未来不要说了.

#include <stdio.h>

//6.0 程序等待函数
extern void sh_pause(void);
//6.0 等待的宏 这里 已经处理好了
#ifndef INIT_PAUSE
#define _STR_PAUSEMSG "请按任意键继续. . ."
#define INIT_PAUSE() \
    atexit(sh_pause)
#endif/* !INIT_PAUSE */

//系统等待函数
void
sh_pause(void)
{
    rewind(stdin);
    printf(_STR_PAUSEMSG);
    getchar();
}

图片 11

考虑一下,只好如此搞,才能通晓函数的入口在哪个地方,不然都找不见函数参数在老大地点. 那也是干什么可变参数须求首先个参数字呈现示评释的原因.

类型名称
描述
相容
va_list
用来保存宏va_arg与宏va_end所需信息
C89

并从中找出 长度 小于 5的 字符串,输出 索引和当前串的内容.代码如下

#include <stdio.h>
#include <stdlib.h>
#include "pthread.h"

//简单的日志宏 fmt必须是字面量字符串
#define ERRLOG(fmt,...) \
        fprintf(stderr,"[%s:%s:%d]" fmt "\r\n",__FILE__,__FUNCTION__,__LINE__,##__VA_ARGS__)

//简单系统等待函数
#define _STR_PAUSE "请按任意键继续. . ."
#define SPAUSE() \
    rewind(stdin),printf(_STR_PAUSE),getchar()

//每个线程打印的条数
#define _INT_CUTS (1000)
//开启的线程数
#define _INT_PTHS (4)
//线程一打印数据
#define _STR_ONES "1111111111111111111111111222222222222222222222222222222222222222222222222222222222223333333333333333333333333333333333333333333333333334444444444444444444444444444444444444445555555555555555555555555555666666666666666666666666666677777777777777777777777777777777777777777777777777778888888888888888888888888888888883333333333333333333333332222222222222222222222211111111111111888888888888888888888888888899999999999999999999999999999999999999990000000000000000000000000000000"
//线程二打印数据
#define _STR_TWO "aaaaaaaaaaaaaaaaaaaaaaassssssssssssssssssssdddddddddddddddddddddddddddddddddddddddfffffffffffffffffffffffffffgggggggggggggggggggggggggggggggggghhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkfffffffffffffffffffffffffffffffffffffffffoooooooooooooooooooooooppppppppppppppppppppppppppppvvvvvvvvvvvvvvvvbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbdddddddddddddds"
//线程三打印数据
#define _STR_THRE "AAAAAAAAAAAAAAAAAAAAQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOPPPPPPPPPPPPPPPPPPPPPPPBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMDDDDDDDDDDDDDDDDDDDDDDDDDDDSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSCCCCCCCCCCCCCCCCCCCCCCGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGSSSCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCFFFFFFFFFFFFFFFF"
//线程四打印数据
#define _STR_FIV "你好好的打打打假摔帝卡发的啥都就看见大大淡蓝色空间对手卡就考虑到就阿里'省空间打算加快递费的数量级匮乏绿豆沙圣诞快乐发送的房间打扫房间卡萨丁就卡机了速度快龙卷风撒娇考虑到房间里邓丽君分手的距离看法就立刻发家里睡觉了舒服大家啦的酸辣粉就看见了看法就李开复撒地方就拉近了看法就困啦风刀霜剑快乐付京东坑垃圾费即可复读机啊健康路附近啊范德萨晶晶啊加合法的考虑加对方说对啦地方睡觉了啥打法来空间浪费大家来看范德萨龙卷风就阿里你好好的打打打假摔帝卡发的啥都就看见大大淡蓝色空间对手卡就考虑到就阿里'省空间打算加快递费的数量级匮乏绿豆沙圣诞快乐发送的房间打扫房间卡萨丁就卡机了速度快龙卷风撒娇考虑到房间里邓丽君分手的距离看法就立刻发家里睡觉了舒服大家啦的酸辣粉就看见了看法就李开复撒地方就拉近了看法就困啦风刀霜剑快乐付京东坑垃圾费即可复读机啊健康路附近啊范德萨晶晶啊加合法的考虑加对方说对啦地方睡觉了啥打法来空间浪费大家来看范德萨龙卷风就阿里"

//全局测试
static FILE *__txt;
//写入测试文件路径
#define _STR_PATH "log.txt"

//线程启动函数
void *start_printf(void *arg);

int main(int argc, char *argv[])
{
    pthread_t ths[_INT_PTHS];
    int i, j;
    int rt;

    puts("printf 线程是否安全测试开始");

    if ((__txt = fopen(_STR_PATH, "w")) == NULL) {
        ERRLOG(_STR_PATH "文件打开失败");
        exit(-1);
    }

    for (i = 0; i<_INT_PTHS; ++i) {
        rt = pthread_create(ths + i, NULL, start_printf, (void*)i);
        if (0 != rt) {
            ERRLOG("pthread_create run error %d!", rt);
            goto __for_join;
        }
    }

__for_join:
    //等待线程结束
    for (j = 0; j<i; ++j)
        pthread_join(ths[j], NULL);//索引访问错误

    puts("printf 线程是否安全测试结束");

    SPAUSE();//等待函数
    return 0;
}

//线程启动函数
void *
start_printf(void *arg)
{
    int idx = (int)arg;
    int i;

    printf("线程%d已经启动!\n", idx);
    for (i = 0; i<_INT_CUTS; ++i) {
        switch (idx) {
        case 0:
            fprintf(__txt, _STR_ONES);
            break;
        case 1:
            fprintf(__txt, _STR_TWO);
            break;
        case 2:
            fprintf(__txt, _STR_THRE);
            break;
        case 3:
            fprintf(__txt, _STR_FIV);
            break;
        default:
            printf("idx => %d 取你吗的.\r\n", idx);
        }
    }

    printf("线程%d已经关闭!\n", idx);
    return (void*)idx;
}

图片 12

  到那里基本就终止,有点一曝十寒,但是printf
2千行代码,若是解析起来,其实相当于说白话.熟练了都是布署和业务.

自小编批评结果是尚未出现乱序现象, 前边看 完<<posix 多线程程序设计>> 之后, 它那里有如此一句话,posix必要ANSI C 中标准输入输出函数式线程安全的.

在条分缕析在此以前,摘了3个 表格,看一下可能会简单通晓一点.如下

// 
// 这个宏是为了编译器字节对齐用的,用sizeof(int) 字节数进行对齐
// 
// 简化一下 sizeof(int) - 1 假定为 3,(当前2015年11月22日就是3) 
// _INTSIZEOF(n) => ((sizeof(n) + 3 ) & ~3 )
// 举个例子
//   _INTSIZEOF(int) => 4
//   _INTSIZEOF(char) => 4
//  _INTSIZEOF(double) => 8
//  _INTSIZEOF(short) => 4
// 因为编译器有内存位置调整,具体参见 struct 内存布局,毕竟都是C基础.编译器这样做之后,访问速度回快一些,根据地址取值的次数会少一些.
 #define _INTSIZEOF(n)          ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))

1.C中可变参数机制