printf重定向的相关总结

2023-05-16

简介

实现printf重定向有多种方式,下面一一介绍。

linux环境下

虽然linux系统的默认标准输出设备是显示器,但是我们可以把printf打印输出的内容重定向到其他设备或文件。方法如下:

方法1:

打开一个普通文件,把它的文件描述符指定为标准输出的文件描述符,这样printf打印输出的数据会重定向到这个普通文件。

示例如下:

//实现printf打印输出重定向功能示例1

#include <stdio.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
	
	printf("hello,zzc\n");

	//保存标准输出的文件描述符	
	int stdout_fd = dup(STDOUT_FILENO);

	//打开一个文件,获取文件描述符
	int fd = open("./2.c", O_RDWR|O_APPEND, 0666);
	if(fd < 0)
	{
		printf("open a file fail\n");
	}

	//指定fd为标准输出的文件描述符
	dup2(fd, STDOUT_FILENO);
	
	printf("standard output file descriptor has changed");
	//刷新标准输出的IO缓冲区
	fflush(stdout);
	
#if 1
	//恢复为默认的标准输出,有两种方式
	//方式一,把之前保存的文件描述符重新指定为标准输出的文件描述符
	dup2(stdout_fd, STDOUT_FILENO);
#else
	//方式二,打开当前的控制终端设备文件(文件路径通过tty命令获取),获取文件描述符
	int tty_fd = open("/dev/pts/0", O_RDWR, 0666);
	dup2(tty_fd, STDOUT_FILENO);
#endif
	printf("standard output file descriptor has restored\n");

	return 0;
}

方法2:

使用freopen函数把文件关联到标准输出,这样printf打印输出的数据会重定向到该文件。

示例如下:

//实现printf打印输出重定向功能示例2

#include <stdio.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
	FILE *p = NULL;

	//freopen()函数的主要用途是更改与标准文本流(stderr、stdin或stdout)相关联的文件。
	p = freopen("./2.c", "a", stdout);
	if(NULL == p)
	{
		perror("freopen ./2.c fail");
		return -1;
	}

	printf("standard output has changed\n");
	
	//恢复为默认的标准输出
	p = freopen("/dev/pts/0", "r+", stdout);
	if(NULL == p)
	{
		perror("freopen fail");
		return -1;
	}
	
	printf("standard output has restored\n");

	return 0;
}

另外,如果想刷新标准IO缓冲区,可以在printf 之后调用fflush。

IO重定向

使用重定向符号进行IO重定向。
常见的重定向符号和功能如下图:

输出重定向:
1234
输入重定向:
1
绑定重定向:
1

实例:

显示当前目录文件test test2(test2实际不存在)
2
正确输出与错误输出都显示在屏幕了,现在需要把正确输出写入note.txt
(1> 可以省略,不写,默认输出至标准输出)
1234
把错误输出,不输出到屏幕,输出到err.txt
4
继续追加把输出写入note.txt err.txt, “>>” 是追加操作符
12
将错误输出信息关闭
34
注:
1、&[n] 代表是已经存在的文件描述符,&1 代表输出 &2代表错误输出 &-代表关闭与它绑定的描述符
2、/dev/null 这个设备,是linux 中黑洞设备

关闭所有输出:
关闭1,2文件描述符
3
将1,2输出转发给/dev/null设备
1
将错误输出2绑定给正确输出1,然后将正确输出发送给/dev/null设备(&代表标准输出,错误输出;将标准输出与错误输出 重定向到/dev/null)
5
使用标准输入,在a.txt文件中写入"hello world"
注:在shell编程中,“EOF"通常与”<<“结合使用,”<<EOF"表示后续的输入作为子命令或子shell的输入,直到遇到"EOF"
1

MCU环境下(以stm32为例)

在不同的开发环境下,我们有多种手段可以对printf打印输出的数据进行重定向,目的是方便我们调试程序。

首先我们要确定开发环境是什么样的,是软件仿真,是硬件仿真,还是产品功能运行。不同的开发环境,有不同的重定向printf内容的方法。

在实时性上,RTT > SWO >串口 >半主机模式;本文不会讲述RTT、半主机模式,有兴趣的朋友请自行查阅资料。

1、软件仿真

在软件仿真的环境下,以keil mdk工程为例,我们使用微库,然后把printf函数重定向到usart串口。

printf重定向代码如下所示:

#include <stdio.h>
int fputc(int ch, FILE * f)
{
    //等待串口数据发送完毕
    while((USART1->SR & USART_FLAG_TC) == 0);
    
    //发送下一个字符
    USART1->DR = (uint8_t)ch;

    return ch;
}

运行效果如下图所示:
1234567

2、硬件仿真

目标开发板通过仿真器(JLINK、ULINK、STLINK等)连接到PC调试主机,在这种环境下,我们可以把printf输出的数据重定向到串口;也可以重定向到ITM端口,通过SWO线把数据发送给PC。

串口重定向的方法和上述软件仿真一样,这里不再复述,下面着重介绍ITM方式。

重定向到ITM:
在Cortex-M3\M4\M7系列MCU中,内核的调试组件有一个仪器跟踪宏单元(ITM) 。请注意如果你的芯片是 Cortex-M0 或者其他ARM内核,不支持ITM。

下面介绍如何把printf打印输出的数据重定向到ITM端口。

硬件连接:

我们都知道SWD接口正常使用是四根线。而使用ITM机制需要多用SWD的一根线:SWO。
先找到link接口SWO引脚的位置,再找到目标开发板上SWO引脚的位置,然后用杜邦线把两个引脚连接起来。

软件配置

第一步: 添加重定向文件

新建一个文件(文件名自定义),添加到mdk工程,文件的内容如下:

#include <stdio.h>  
  
#define ITM_Port8(n)    (*((volatile unsigned char *)(0xE0000000+4*n)))  
#define ITM_Port16(n)   (*((volatile unsigned short*)(0xE0000000+4*n)))  
#define ITM_Port32(n)   (*((volatile unsigned long *)(0xE0000000+4*n)))  
#define DEMCR           (*((volatile unsigned long *)(0xE000EDFC)))  
#define TRCENA          0x01000000  
  
struct __FILE { int handle; /* Add whatever you need here */ };  
    FILE __stdout;  
    FILE __stdin;  
      
int fputc(int ch, FILE *f)   
{  
    if (DEMCR & TRCENA)   
    {  
        while (ITM_Port32(0) == 0);  
        ITM_Port8(0) = ch;  
    }  
    return(ch);  
}  

说明:

1、这个文件用于重定义fputc函数;因为printf函数的底层实现就是fputc,所以需要重定义这个函数,在这个函数里面把printf打印输出的数据重定向到ITM端口
2、上述文件中默认使用ITM的port0,当然可以使用其他的端口。

关于ITM的配置,可以参考以下描述:
123
更多详细描述请参阅stm32有关的参考手册。

第二步:新建一个配置文件(STM32DBG.ini),用于stm32 debugger初始化,把这个文件放在mdk工程下。文件内容如下:

/******************************************************************************/  
/* STM32DBG.INI: STM32 Debugger Initialization File                           */  
/******************************************************************************/  
// <<< Use Configuration Wizard in Context Menu >>>                           //   
/******************************************************************************/  
/* This file is part of the uVision/ARM development tools.                    */  
/* Copyright (c) 2005-2007 Keil Software. All rights reserved.                */  
/* This software may only be used under the terms of a valid, current,        */  
/* end user licence from KEIL for a compatible version of KEIL software       */  
/* development tools. Nothing else gives you the right to use this software.  */  
/******************************************************************************/  
  
  
FUNC void DebugSetup (void) {  
// <h> Debug MCU Configuration  
//   <o1.0>    DBG_SLEEP     <i> Debug Sleep Mode  
//   <o1.1>    DBG_STOP      <i> Debug Stop Mode  
//   <o1.2>    DBG_STANDBY   <i> Debug Standby Mode  
//   <o1.5>    TRACE_IOEN    <i> Trace I/O Enable   
//   <o1.6..7> TRACE_MODE    <i> Trace Mode  
//             <0=> Asynchronous  
//             <1=> Synchronous: TRACEDATA Size 1  
//             <2=> Synchronous: TRACEDATA Size 2  
//             <3=> Synchronous: TRACEDATA Size 4  
//   <o1.8>    DBG_IWDG_STOP <i> Independant Watchdog Stopped when Core is halted  
//   <o1.9>    DBG_WWDG_STOP <i> Window Watchdog Stopped when Core is halted  
//   <o1.10>   DBG_TIM1_STOP <i> Timer 1 Stopped when Core is halted  
//   <o1.11>   DBG_TIM2_STOP <i> Timer 2 Stopped when Core is halted  
//   <o1.12>   DBG_TIM3_STOP <i> Timer 3 Stopped when Core is halted  
//   <o1.13>   DBG_TIM4_STOP <i> Timer 4 Stopped when Core is halted  
//   <o1.14>   DBG_CAN_STOP  <i> CAN Stopped when Core is halted  
// </h>  
_WDWORD(0xE0042004, 0x00000027);  // DBGMCU_CR  
_WDWORD(0xE000ED08, 0x20000000);   // Setup Vector Table Offset Register  
}  
  
DebugSetup();                       // Debugger Setup  

第三步:配置mdk工程,如下图:

配置初始化文件
1234
选择SW接口(我这里没有接link,所以有些信息没有显示)
12345

core clock需要设置为你的系统时钟频率,如果你的cpu主频是72MHz,那就设置为72MHz;
跟踪功能需要使能,另外ITM端口默认使用端口0,当然你也可以使用其他端口。

123

第四步: 烧录程序,启动调试

打开debug viewer,你会发现printf打印输出的数据会显示在这个窗口上。那是因为printf重定向到了ITM端口,然后再通过仿真器的SWO线把数据传回PC。
1234
打印出乱码是因为我打印了中文。

注意事项

1、如果使用微库,不需要关闭半主机模式,因为并不会进入半主机模式。
2、如果使用MDK提供的标准库(不勾选微库),就需要关闭半主机模式。方法就是上述重定向文件中添加下面这句话:

#pragma import(__use_no_semihosting_swi)  

这句话意思是告知连接器不从C库链接使用半主机的函数。

如果你使用的是AC5编译器,是没有问题的;如果你使用的是AC6编译器,你可能会遇到问题:编译器会报错提示 #pragma import(__use_no_semihosting_swi) 这个命令AC6并不支持。

你可以添加下面这句话来解决这个问题:

__ASM (".global __use_no_semihosting");

3、开发板独立运行(不带仿真器)

不接仿真器,开发板独立运行,这种场景下可以使用串口重定向,这里不再复述。

拓展

一、输入输出重定向(ITM方式)

我们也可以重定向输入,本来是从标准输入设备输入,但是开发板没有这个东西,所以我们可以从PC的标准输入设备输入。

新建一个重定向文件,文件内容如下:(对标准输入和输出都做了重定向)

#pragma import(__use_no_semihosting_swi)  
  
struct __FILE { int handle; /* Add whatever you need here */ };  

FILE __stdout;  
FILE __stdin;  
      
int fputc(int ch, FILE *f)   
{  
    return ITM_SendChar(ch);  
}  
  
volatile int32_t ITM_RxBuffer;  
int fgetc(FILE *f)  
{  
  while (ITM_CheckChar() != 1) __NOP();  
  return (ITM_ReceiveChar());  
}  
  
int ferror(FILE *f)  
{  
    /* Your implementation of ferror */  
    return EOF;  
}  
  
void _ttywrch(int c)  
{  
    fputc(c, 0);  
}  
  
int __backspace()  
{  
    return 0;  
}  
void _sys_exit(int return_code)  
{  
label:  
    goto label;  /* endless loop */  
}  

keil工程编译之后运行效果如下:(先从PC键盘输入一个整数,然后打印出这个整数)
123

需要说明以下几点:

1、ITM_SendChar、ITM_CheckChar、ITM_ReceiveChar这几个函数都是core_cm3.h/core_cm4.h/core_cm7.h文件中定义的

2、scanf依赖的函数共有两个,fgetc和__backspace都需要实现,如果缺少__backespace函数,则scanf无法从Debug Viewer Dialog 窗口获取输入

3、如果编译报错,缺少以上某些函数,那就需要添加这几个函数

二、在GCC中使用标准库重定向printf

在Gcc中重定向printf函数时要注意以下两点:

  • 与重定义fputs()函数一样,在使用gcc编译器的时候,需要重新定义_write函数;
  • gcc中没有MicroLib,只能使用标准库;

重定向代码如下所示:

#include <stdio.h>
int _write(int fd, char *ptr, int len)  
{ 
	int ret = len;
	while(len)
	{
		USART_SendData(USART1, *(char *)ptr);
		len--;
  	}
	return ret;
}

总结

本文汇总了printf函数在linux系统下和在mcu环境下重定向的几种方法,比如重定向到串口、重定向到ITM端口、重定向到文件等,其实还可以重定向到RTT。方法还是很多的,需要大家一起探索。

实际上,还是要根据自己的开发环境来选择合适的方法,技术在不断发展,以后肯定会出现更多更好的调试方法,方便我们调试、提高工作效率才是最终的目的。

参考资料

参考手册

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

printf重定向的相关总结 的相关文章

  • Printf 函数格式化程序

    有以下简单的C 代码 include
  • 为什么 String 的 format(Object...args) 定义为静态方法?

    我想知道为什么Java5 及以上版本提供了使用 String 类中的静态方法的 printf 样式格式化程序 如下所示 public static String format String format Object args 代替 pub
  • MinGW GCC:“未知转换类型字符'h'”(snprintf)

    好的 我在 Windows 7 上使用 MinGW GCC 4 6 2 编译 C 文件时遇到了一个奇怪的问题 该文件包含以下 C 代码 include
  • Xcode 在 C++ 控制台中不显示任何内容

    我只是想将 Xcode 用于一个非常小的 C 项目 并且想看看 控制台中有一些打印 问题是我没有看到任何东西 我尝试运行一个非常简单的代码 include
  • 在 Matlab 中将元胞数组打印为 .txt

    我有一个元胞数组 需要根据特定格式打印在 txt 文件中 我尝试过一些在线帮助 包括matlabcentraldlmcell但即便如此也没有给我想要的答案 分隔符是 t cellarray AAPL 2 20 2011 100 5 MSFT
  • 如何使用 write 系统调用将 int 写入文件并完全按照写入方式读取它们?

    如何使用 UNIX 的 write 系统调用将 int float 或其他类型写入文件 我想这样做而不使用任何 lib 函数 例如fprintf or fwrite 我想使用文件描述符而不是FILE 再次打开后 文件必须完全按照写入的方式读
  • 使用 printf 在 c 中 fork() [重复]

    这个问题在这里已经有答案了 有 2 个不同的程序 它们都很小 例如 int main printf print hello fork int main printf print hello n fork 输出 1 是 print hello
  • 将可变参数传递给 printf [重复]

    这个问题在这里已经有答案了 我想要一个辅助功能log它主要执行以下操作 log file array has d elements n 10 writes 2014 02 03 16 33 00 array has 10 elements
  • php sprintf() 包含外来字符?

    接缝就像 sprintf 有外来字符的问题 还是我做错了什么 看起来它在从字符串中删除像 这样的字符时有效 有必要吗 我希望以下几行能够正确对齐以生成报告 2011 11 27 A1823 Ref Leif 12 873 00 18 98
  • 如何使用 sprintf 附加字符串?

    我面临着一个严重的问题sprintf 假设我的代码片段是 sprintf Buffer Hello World sprintf Buffer Good Morning sprintf Buffer Good Afternoon 几百次冲刺
  • 在没有正确原型的情况下调用 printf 是否会引发未定义的行为?

    这个看起来无辜的程序是否会调用未定义的行为 int main void printf d n 1 return 0 是的 调用printf 没有适当的原型 来自标准头
  • 向 printf 传递太多参数

    任何已经工作了一周以上的 C 程序员都遇到过因调用而导致的崩溃printf格式说明符多于实际参数 例如 printf Gonna s and s s crash burn 然而 当你通过时 是否会发生类似的糟糕事情 too manyprin
  • 使用 sprintf 打印元素数量可变的向量

    在下面的代码中 我可以打印向量中的所有元素item用空格分隔为 item 123 456 789 sprintf d d d item ans 123 456 789 我怎样才能做到这一点而不必输入那么多 d作为元素的数量item 最简单的
  • printf 中的 # 标志如何工作?

    include
  • Printf:Java 和 C 实现的差异

    今天我发现我无法使用 将宽度或精度参数传递给 Java 的实现printf 也就是说 以下论证printf在 C 中有效 但在 Java 中无效 d 10 3 d 10 3 0 d 10 3 5f 11 1 0 9 11 f 5 1 0 9
  • 字符串格式化表达式 (Python)

    字符串格式化表达式 This is d s example 1 nice 字符串格式化方法调用 This is 0 1 example format 1 nice 我个人更喜欢方法调用 第二个示例 以提高可读性 但由于它是新的 因此随着时间
  • 左填充 printf 带空格

    使用 printf 时如何在字符串左侧填充空格 例如 我想打印 Hello 前面有 40 个空格 另外 我要打印的字符串由多行组成 我需要单独打印每一行吗 编辑 为了明确起见 我希望在每行之前打印 40 个空格 如果您希望在 40 个字符宽
  • 需要在python中找到print或printf的源代码[关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 help reopen questions 我正在做一些我不能完全谈论的事情 我
  • printf() 格式化十六进制

    为什么当将十六进制数字打印为带前导零的 8 位数字时 08X not显示相同的结果0x 08X 当我尝试使用前者时 08格式化标志已被删除 并且它不适用于仅8 The 部分给你一个0x在输出字符串中 这0和x计算您在列表中列出的 8 个字符
  • 可以使用多个 _Generic 创建字符串文字吗?

    有没有办法使用 Generic在同一表达式中多次使用关键字来创建单个字符串文字 我正在寻找的是一种方法 例如生成要传递给的单个格式字符串printf 所有转换说明符都适应正确的类型 写作时this https stackoverflow c

随机推荐

  • 字节序与比特序详解

    字节序的定义 几种类型的字节序 cpu字节序外部bus字节序设备字节序网络协议字节序 Ethernet协议字节序IP协议字节序 编译字节序 比特序的定义字节序与bit序的转换结构体的位域 字节序的定义 字节序就是说一个对象的多个字节在内存中
  • 【动态规划】01背包问题

    问题描述 有n个物品 xff0c 它们有各自的体积和价值 xff0c 现有给定容量的背包 xff0c 如何让背包里装入的物品具有最大的价值总和 xff1f 为方便讲解和理解 xff0c 下面讲述的例子均先用具体的数字代入 xff0c 即 x
  • 献给初学labview数据采集的初学者

    前言 xff1a 参考来源 xff1a http bbs elecfans com jishu 209658 1 5 html xff0c 感谢原作者 zhihuizhou 这里的内容只针对NI的数据采集卡 xff0c 不保证适用于其它公司
  • 如何从科学论文中实现一个算法

    原文 xff1a http codecapsule com 2012 01 18 how to implement a paper 作者 xff1a Emmanuel Goossaert 本文是从科学论文中实现算法的简短指南 我从书籍和科学
  • 国内C/C++刷题网站汇总

    作者 xff1a Luau Lawrence 链接 xff1a https www zhihu com question 25574458 answer 31175374 来源 xff1a 知乎 Welcome To PKU JudgeOn
  • 华为16道经典面试题

    面试过程中 xff0c 面试官会向应聘者发问 xff0c 而应聘者的 回答将成为面试官考虑是否接受他的重要依据 对应聘者而言 xff0c 了解这些问题背后的 猫腻 至关重要 本文对面试中经常出现的一些典型问题进行了整理 xff0c 并给出相
  • 语音信号的预加重和加窗处理

    原文转载于 xff1a http blog csdn net ziyuzhao123 article details 12004603 非常感谢 一 语音信号的预加重 语音信号的预加重 xff0c 目的是为了对语音的高频部分进行加重 xff
  • 单独编译和使用webrtc音频增益模块(AGC)

    原文转载于 xff1a http www cnblogs com mod109 p 5767867 html top 非常感谢 webrtc的音频处理模块分为降噪ns xff08 nsx xff09 xff0c 回音消除aec xff08
  • 功率谱和频谱的区别

    生活中很多东西之间都依靠信号的传播 xff0c 信号的传播都是看不见的 xff0c 但是它以波的形式存在着 xff0c 这类信号会产生功率 xff0c 单位频带的信号功率就被称之为功率谱 它可以显示在一定的区域中信号功率随着频率变化的分布情
  • 如何解决在rviz中,路径规划导航时,点击2D Pose estimate后机器人位置没有改变,终端也没有反应的问题

    在rviz中 xff0c 点击2D Pose estimate后机器人位置没有改变 xff0c 终端也没有反应 xff0c 通常这种情况就是由于客户端和服务端IP地址不一致导致的 xff0c IP地址有时候系统会自动变更 xff0c 这里我
  • 五款免费开源的语音识别工具

    按 xff1a 本文原作者 Cindi Thompson xff0c 美国德克萨斯大学奥斯汀分校 xff08 University of Texas at Austin xff09 计算机科学博士 xff0c 数据科学咨询公司硅谷数据科学
  • 动态内存分配

    动态内存分配 常见的内存分配的错误 先上一个内存分配的思维导图 便于联想想象 xff0c 理解 xff1a 首先我们介绍一下内存分配的方式 xff1a 1 在静态存储区域中进行分配 内存在程序编译的时候就已经分配好 xff0c 这块内存在程
  • UEFI架构

    UEFI架构 UEFI提供系统化的标准方法 xff0c 加载驱动并管理他们之间的交互 前言 xff1a 感谢uefi blog UEFI 提供了一个标准接口 xff0c 以便在硬件发生变更时固件能提供足够信息而保证操作系统不受影响 它包含有
  • C++调试工具(未完)

    C 43 43 调试相关命令 ld so conf https blog csdn net Bruce 0712 article details 78816790相关的命令 ar nm 目标格式文件分析 xff0c 所以也可以分析 a文件
  • 11_UART串口

    11 UART 文章目录 11 UART1 串口连接芯片图2 串口传输一个字节的过程3 发送接收过程4 编写UART函数4 1 初始化函数uart0 init 4 1 1 设置引脚用于串口4 1 2 使能上拉4 1 3 设置波特率4 1 4
  • 汇编指令:LDMIA、LDMIB、LDMDB、LDMDA、STMIA、LDMFD、LDMFA、LDMED、LDMEA

    ARM指令中多数据传输共有两种 xff1a LDM load much xff1a 多数据加载 将地址上的值加载到寄存器上 STM store much xff1a 多数据存储 将寄存器的值存到地址上 主要用途 xff1a 现场保护 数据复
  • C++ 实现 发送HTTP Get/Post请求

    1 简述 最近简单看了一下关于HTTP请求方面的知识 xff0c 之前一直用Qt来实现 xff0c 有专门HTTP请求的QNetworkAccessManager类来处理 xff0c 实现也比较简单 xff0c 这里主要讲解一下用C 43
  • [Simple] 洗牌算法

    题目要求 xff1a 平时洗牌是两打牌 xff0c 交叉洗在一起 也就是开始 1 2 3 4 5 6 7 8 第一次 1 5 2 6 3 7 4 8 第二次 1 3 5 7 2 4 6 8 第k次 给你一个数组a 2N xff0c 要求在O
  • 【PID控制原理及其算法】

    前言 本文以自己的学习过程总结而来 xff0c 将自己的经验写出来以供大家一起学习 xff0c 如有错误请多指教 一 PID是什么 xff1f PID就是比例 积分 微分 xff0c PID算法可以说是在自动控制原理中比较经典的一套算法 x
  • printf重定向的相关总结

    简介 实现printf重定向有多种方式 xff0c 下面一一介绍 linux环境下 虽然linux系统的默认标准输出设备是显示器 xff0c 但是我们可以把printf打印输出的内容重定向到其他设备或文件 方法如下 xff1a 方法1 xf