20 printf 的调试

news2025/1/11 4:16:43

前言

在最开始的 cmd 编程中, 我们会使用到的最常见的输出, 包括一些时候调试的时候 我们最常使用到的函数 那肯定是 printf 了 

我们这里来调试一下 这个 printf 

还有一个原因是 之前在调试 malloc 的时候, malloc 虚拟内存分配的调试(1) 可以发现, 不仅仅是在 malloc 的时候调用了 malloc, 在 printf 的时候也调用了 malloc 

然后 我们这里来看一下 具体的情况, 以及一些 抽象的理解映射到 代码上面怎么处理的 

也可以参考一下 printf 命令 二者有一些相似之处, 不过一个是 可执行程序, 一个是 glibc 提供的库函数 

测试用例

同样是基于 这个最简单的 case, 不过这里 我们关注的是 printf 

root@ubuntu:~/ClionWorkStations/HelloWorld# cat Test01Sum.c 
#include "stdio.h"

int main(int argc, char** argv) {

int x = 2;
int y = 3;
int z = x + y;

char *p1 = (char *)malloc(20);
char *p2 = (char *)malloc(20);

*p1 = '1';
*(p1+1) = '2';

free(p2);

void *p3 = realloc(p1, 10);
printf("p1 : 0x%x - 0x%x \n", p1, p2);
printf("p3 : 0x%x - 0x%x \n", p3, p3);

}

printf 的核心流程

看一下 我们这里断点的位置, 确实是在 main 中的第一个输出的位置 

大致的流程是 找到第一个占位符的位置, 然后输出 第一个占位符之前的常量字符串 到 s 

然后之后 从第一个占位符开始 循环处理每一个 占位符 

处理当前占位符, 结合 占位符指定的格式 分为数字类输出 和 字符串类输出, 分别在 process_arg, process_string_arg 中输出 

这两个宏中 做的不同格式的 格式化的相关操作 

并且 查找下一个占位符, 输出两个占位符之间的常量字符串, 如果没有下一个占位符, 输出剩余的常量字符串到 s 

我们这里是按照 十六进制 输出 p1地址, 因此 走的是 process_arg, 处理之前 s 中内容如下 

 process_arg 宏处理完成之后, s 中的内容增加了作为参数的 p1 的地址的 十六进制 表示 

查找下一个 占位符, 输出当前占位符 和 下一个 占位符之间的 常量字符串 

其实 和 printf 程序 还是有一些 相似之处的 

printf 的输出

 如果当前 buf 是 LINE_BUF, 并且 正在输出 

则当前 待输出数据中包含 回车 的时候, 必须要 输出到设备, 输出内容到 回车 的位置 

然后 将带输出字符串 拷贝到 buf 中 

如果 待输出字符串大小超过了 缓冲区间大小, 或者是 行缓冲 碰到了 换行, 则要求输出内容到设备 

然后 如果还剩余待输出内容, 则输出内容到 缓冲/设备 

然后 如果还剩余待输出内容, 则输出内容到 缓冲/设备 

判断根据 blockSize 对齐之后需要输出多少字节的内容到 缓冲/设备, 然后递归调用 new_do_write 来输出 (todo/block_size) 个blockSize 的内容到 缓冲/设备 

然后 将剩余的待输出数据 输出到 缓冲 

比如 todo 为 248, blockSize 为 100, 则递归调用 new_do_write 输出 200 个字节到 缓冲/设备 

剩余的 48 字节通过下面的 IO_default_xsputn 输出到 缓冲 

printf 的持久化输出是基于系统调用 write 来实现的 

将缓冲的数据 通过 write 输出到 fp 中 

在内核层面, 看到这一次 write 如下[这里调整了应用程序, 输出 和 上下文输出不太一样, 不过意思一样]

这里 有空我们, 展开来讲讲, 这里的 tty 的输出 

printf 分配的缓冲区间

这里的 _IO_BUFSIZ 的宏为 8192, 下面的 st.st_blksize 为 1024 

优先取的后者, 因此 printf 输出 buf 的大小为 1024 字节

并且 上面, 如果是 字符设备, 还将 buf 增加了 行缓冲 的标记 

然后 这里分配的 缓冲区间, 也是来自于 malloc, 因此 分配的区间的地址信息, 可以根据 p1, p2 的地址 推导出来, 你可以试试 

一个简单地测试方式是 malloc 一个 p3, 然后 观察 p2, p3 之间的空间, 这块空间就是 printf 的缓冲区间 

修改 用例如下 

root@ubuntu:~/ClionWorkStations/HelloWorld# cat Test01Sum.c 
#include "stdio.h"

int main(int argc, char** argv) {

int x = 2;
int y = 3;
int z = x + y;

char *p1 = (char *)malloc(20);
char *p2 = (char *)malloc(20);

*p1 = '1';
*(p1+1) = '2';

free(p2);
printf("p1 : 0x%x - 0x%x \n", p1, p2);

void *p3 = malloc(10);
printf("p1 : 0x%x - 0x%x \n", p1, p2);
printf("p3 : 0x%x - 0x%x \n", p3, p3);

}

输出如下, 可以推导出 printf 的 buf 地址为 0x602050, 大小为 (0x602440 - 0x602030) = 0x410 = 1040 

计算方式为 ALIGN_UP((1024 + 8), 16) = 1040 

地址为 p2 之后的空间, p3 之前的空间 

p1 : 0x602010 - 0x602030 
p1 : 0x602010 - 0x602030 
p3 : 0x602440 - 0x602440 

 完

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/475902.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Codeforces Round 865 (Div. 2)

6 problems. ABC过, DE没想出来, F没看. https://codeforces.com/contest/1816 A. Ian Visits Mary 分析 - AC 每次跳跃,横纵互质。 限于数据量,不能枚举。 1与任何数互质。考虑从(0,0)跳到(1,y),这一步一定合法;再从(1,y)跳到…

Netty小白入门教程

一、概述 1.1 概念 Netty是一个异步的基于事件驱动(即多路复用技术)的网络应用框架,用于快速开发可维护、高性能的网络服务器和客户端。 1.2 地位 Netty在Java网络应用框架中的地位就好比,Spring框架在JavaEE开发中的地位。 以下的框架都使用了Nett…

C++017-C++文件读写应用

文章目录 C017-C文件读写应用C文件读写应用CSP-J目标1. 文件的基本概念、文本文件的基本操作关闭文件 文件操作-写入文本文件2.文件读写操作基本案例seekg() 和 tellg() 函数来读取文件中的数据 2. 文本文件类型与二进制文件类型3. 文件重定向、文件读写等操作 在练习&#xff…

56.网页设计规则#4_图标

使用好的图标 使用一个好的图标包,有大量的免费和付费图标包 图标工具推荐: ● Phosphor icons ● ionicons ● ICONS8 只使用一个图标包。不要混合不同图标包中的图标 使用SVG图标或图标字体。不要使用位图图像格式(.jpg and .png)! 调整网站个性!圆…

Android 中的跨进程数据块传递

Android 的 Binder 进程间通信机制主要用于实现远程过程调用 RPC,Android 系统中进程之间的大块数据传递,如音频数据,出于效率等原因,一般不直接用 Binder 机制。Binder 库提供了基于共享内存外加 Binder 机制的跨进程文件描述符传…

每月一书(202304)《RxJava2.x实战》

[TOC] 又到了每月一书的时间,本月阅读的是技术相关书籍《RxJava2.x实战》,下面分享一下我阅读完后的体会。 主要内容 本书主要介绍了RxJava这个框架,框架版本是2.x。主要内容包含三大部分: 框架的原理和使用方法框架中各类操…

git alias

git alias 其实之前就用过一些 alias,比如说 git reflog show 就是 git log -g --abbrev-commit --prettyoneline 的 alias,一般 alias 可以存储到 git 的 config 文件,repo 等级的在 .git 下,global 的一般在 ~/.gitconfig 或者…

【flask】三种路由和各自的比较配置文件所有的字母必须大写if __name__的作用核心对象循环引用的几种解决方式--难Flask的经典错误上下文管理器

三种路由 方法1:装饰器 python C#, java 都可以用这种方式 from flask import Flask app Flask(__name__)app.route(/hello) def hello():return Hello world!app.run(debugTrue)方法2: 注册路由 php python from flask import Flask app Flask(__name__)//app…

【前端面经】JS-事件循环

什么是事件循环(Event Loop)? 众所周知, Javascript是一门单线程的语言, 单线程即同一时间只能做一件事, 但这并不意味着JavaScript在执行代码的过程中就会一直阻塞,而解决单线程不阻塞的这个机制就叫做事件循环(Event Loop), 也就是同步和异步的概念. 任务执行流程 在JS中…

PS滤镜插件-Nik Collection介绍

PS滤镜插件-Nik Collection介绍 什么是Nik CollectionNik Collection都包含什么? 什么是Nik Collection Nik Collection是一款PS滤镜插件套装,其包含了八款PS插件,功能涵盖修图、调色、降噪、胶片滤镜等方面。Nik Collection 作为很多摄影师…

redhat 安装oracle 11g

这里写目录标题 1、数据库下载和安装文档1.2、安装文档1.3、license种类解释( XE版 标准本 个人版 企业版)1.4、在安装完oracle后再创建数据库1.5、DBA的文档1.6、Automatic Storage Management Administrators Guide1.7、数据库备份恢复手册1.8、Overvi…

系统集成项目管理工程师 笔记(第15章 信息(文档)和配置管理)

文章目录 软件文档的分类(1)开发文档:描述开发过程 本身(2)产品文档:描述开发过程的 产物(3)管理文档:记录项目管理的信息 文档的质量可以分为四级(1&#xf…

03_Uboot网络命令与MMC和文件操作命令

目录 网络操作命令 ping命令 nfs 命令 tftp 命令 EMMC和SD卡操作命令 mmc info命令 mmc rescan命令 mmc list 命令 mmc dev 命令 mmc part命令 mmc read 命令 mmc write 命令 mmc erase 命令 FAT 格式文件系统操作命令 fatinfo 命令 fatls 命令 fstype 命令 …

好的代码风格,如同书法,让你的代码更加漂亮

很多初学者的代码其实都不够“漂亮”,那是因为没有养成好的编码习惯。本篇博客以C语言为例,总结一些好习惯。其实,很多习惯都是肌肉记忆,举个例子:请你写一个程序,输入2个整数并输出它们的和。有些朋友可能…

java中的\t说明

阅读前请看一下:我是一个热衷于记录的人,每次写博客会反复研读,尽量不断提升博客质量。文章设置为仅粉丝可见,是因为写博客确实花了不少精力。希望互相进步谢谢!! 文章目录 阅读前请看一下:我是…

SQLIntegrityConstraintViolationException: Column ‘create_time‘ cannot be null

概述 在使用MySQL MyBatis时遇到的问题,记录一下。 问题 在测试环境里,往MySQL数据表里插入数据时报错:SQLIntegrityConstraintViolationException: Column create_time cannot be null 表结构字段定义: create_time dateti…

【Canvas入门】从零开始在Canvas上绘制简单的动画

这篇文章是观看HTML5 Canvas Tutorials for Beginners教程做的记录,所以代码和最后的效果比较相似,教程的内容主要关于这四个部分: 创建并设置尺寸添加元素让元素动起来与元素交互 设置Canvas的大小 获取到canvas并设置尺寸为当前窗口的大…

Lesson13 IP协议

IP: 提供一种能力,将数据从A主机送到B主机的能力,但不一定会成功 主机 : 配有 IP 地址 , 但是不进行路由控制的设备 ; 路由器: 即配有 IP 地址 , 又能进行路由控制 ; 节点 : 主机和路由器的统称; 协议头格式 如何封装和解包: 定长报头 自描述字段 如何交付(分用) : 8…

Linux驱动之input输入子系统

输入子系统用于实现Linux系统输入设备(鼠标 键盘 触摸屏 游戏杆)驱动的一种框架。Linux内核将其中的固定部分放入内核,驱动开发时只需要实现其中的不固定部分(主要还是和硬件相关的部分),这和platform设备…

离散数学下--- 代数系统

代数系统 定义: 代数系统是用代数运算构造数学模型的方法。 • 通过构造手段生成,所以也称代数结构 • 代数运算:在集合上建立满足一定规则的运算系统 (一)二元运算 二元运算的定义 二元运算需要满足的两个条件&a…