printf("%d",5.01)和printf("%f",5)的输出结果

本文解析了使用printf函数输出不同类型数值时出现的异常结果,详细解释了整型与浮点型在内存中的存储方式及printf函数如何处理这些类型,揭示了5.01输出为大数的现象。

原文地址:http://blog.youkuaiyun.com/yahohi/article/details/7701434

  1. printf("%f\n",5);  
  2. printf("%d\n",5.01);   
  3. printf("%f\n", (float)5);    
  4. printf("%f\n", 5.f);    


输出结果:

看到结果,会感觉非常奇怪。1处怎么会输出0呢?2又为何会显示这么大的一个数呢?

解释:

下面是转自网上的一篇博客的解释

1,之所以没输出5,这是C语言设计的原因。
2,之所以输出0,这是计算机体系结构的问题。

具体来说:

printf函数不会进行任何类型转换,它只是从内存中读出你所提供的元素的值(按照%d,%f等控制字符提示的格式)。C语言设计中,int类型一般是32bit或者16bit,而float一般是64bit,并且有可能使用科学计数保存。这点就和huhugo88所说一样,5在内存中为00000000,00000101。而且5一般都在静态区,程序的静态存储区默认是0,那么当用%f来读时,就会读64bit,也就是会读之前的很多位0,最后按照(有效数字)×(基数2)pow(指数)的方式来取数,自然结果是0

之所以Vc中不允许这种情况,而有些编译器就允许这么输出就是编译器设置的问题。按理说,这样访问内存是属于越界访问,应该禁止。不过只是读,伤害性不大而已。  对于单精度浮点数(32bit),不少c语言编译系统以24位表示小数部分(包括1bit符号位),以8位表示指数部分。 ==========================printf("%d\n",5.01);  为什么输出一个大数?在讲这个题目之前,预备知识,讲一下,printf函数,输入参数是读入缓冲区保存,再按照%?的格式从缓冲区中读出数据,并据此格式解释数据。

有了这个知识之后,在讲程序员面试宝典上看到一个题:

 

#include "stdio.h" 
int main(int argc, char* argv[])   
{   
    printf("%d\n",5.01);   
return 0;   
}   
输出结果为:188978561  
然后开始研究为什么会是这个数?

5.01是double类型,内存中占8个字节,保存在缓冲区。而%d为整型,占4个字节,printf从缓冲区中读入4字节,先读到低32位的数据。也就是说printf输出的应该是5.01以double类型保存数剧的低32位。为了检验此结果是否正确,对比5.01在内存中的表示与输出。

 

#include "stdio.h" 
int main(int argc, char* argv[])   
{   
double d = 5.01;   
int *p = (int *)(&d);   
int rst = 1889785610;   
    printf("1).%x\n",*p);   
    printf("2).%x\n",*(p+1));   
    printf("3).%x\n",rst);   
return 0;   
}   
输出为:   
1).0x70a3d70a   
2).0x40140a3d   
3).0x70a3d70a  
这也就证明了%d输出了5.01的低32低。5.01的double类型,在内存的的表示为0x40140a3d70a3d70a。

事情看似也就完成了。

我又想,如果输入是浮点类型的5.01f,又会发生什么呢?

 

#include "stdio.h" 
int main(int argc, char* argv[])   
{   
float f = 5.01f;     
int *p = (int *)(&f);        
    printf("1).0x%x\n",*p);     
    printf("2).0x%x\n",5.01f);     
return 0;     
}   
输出:   
1).0x40a051ec   
2).0x80000000  
我们发现,此时输出的并不是浮点类型5.01f的内存的表示,这是为什么呢?

然后看到一个说法,是printf会把%f按double类型输出,也就是说会把参数float型的转成double型在输出。

但现在并不是%f,当然用%f显示的是正确的结果。于是我猜测,printf是将所在float型读入的数据都自动的转化为double型了,然后%f就按double处理,而我们这是%d,所以显示的为float转化为double型后的低4字节。

验证此想法:

 

#include "stdio.h" 
int main(int argc, char* argv[])   
{   
double f = 5.01;     
int *p = (int *)(&f);   
    printf("1).0x%x\n",*p);   
    printf("2).0x%x\n",*(p+1));     
    printf("3).0x%x\n",5.01f);     
return 0;     
}   
输出:   
1).0x70a3d70a   
2).0x40140a3d   
3).0x80000000  
但是我们发现结果并不一样,于是我又猜想,也是许printf将float转化为double的方式与默认的方式不一样

5.01d的默认的表示为:0x40140a3d70a3d70a,在上面已经说明了

 

#include "stdio.h" 
int main(int argc, char* argv[])   
{   
    printf("0x%8x\n0x%8x\n",5.01f);   
return 0;   
}   
输出为:   
0x80000000   
0x40140a3d  
与是发现printf将5.01f->5.01d的表示是:0x40140a3d80000000

接着就是看这两个值是否都是为5.01了:

view plaincopy to clipboardprint?

#include "stdio.h" 
int main(int argc, char* argv[])   
{   
int d1[2], d2[2];   
    d1[0]=0x80000000;   
    d1[1]=0x40140a3d;   
    d2[0]=0x70a3d70a;   
    d2[1]=0x40140a3d;   
double *p1 = (double *)d1;   
double *p2 = (double *)d2;   
    printf("1).%f\n",*p1);   
    printf("2).%f\n",*p2);   
return 0;     
}   
输出为:   
1).5.010000   
2).5.010000  
也就证明了0x40140a3d80000000,与0x40140a3d70a3d70a都是5.01d在机器中的表示。前者为5.01f(0x40a051ec)由printf转化为double后的表示,后者为5.01d的默认的表示。

总结:printf将输的浮点型参数全都自动转化为双精度型,且与默认的双精度的表示方法是不同的。最重要一点,printf不安全,类型不安全,要是类型不对了,也许我们就挂了^_^


本文来自优快云博客,转载请标明出处:http://blog.youkuaiyun.com/fengyunjh/archive/2011/03/07/6230164.aspx

 

=======================================================================================================================

通过以上解释,我们大致明白:

1. printf输出float型时,会自动转化成double型;

2. 由于存储时,都是先低位,再高位,同时经过转化成double,前面会取很多0(越界访问);

3. 5.01,打印时按照int来取,只取前四个字节。


<think>我们面对的问题是在ARM A7处理器上使用ARM Compiler 5.01(即armcc)时关闭Semihosting功能。根据引用[4],Semihosting是一种调试技术,它允许目标设备通过调试器使用主机的输入输出功能。但在最终产品中,我们通常需要关闭Semihosting,以避免依赖于主机。 关闭Semihosting的方法通常涉及以下步骤: 1. **修改链接器选项**:在链接时,不使用包含Semihosting实现的库。 2. **重定向标准库函数**:提供自己的底层IO函数实现(如`_sys_open`、`_sys_write`等),以替代Semihosting的实现。 3. **使用`--no_semihosting`编译选项**:ARM Compiler提供了专门的选项来关闭Semihosting。 具体步骤如下: ### 步骤1:在编译链接时添加`--no_semihosting`选项 在ARM Compiler 5.01中,可以通过添加`--no_semihosting`选项来告诉编译器不要使用Semihosting功能。这个选项需要同时用于编译链接阶段。 例如,在命令行中: ``` armcc --no_semihosting -c source.c armlink --no_semihosting object1.o object2.o ``` 或者在Keil IDE中设置: - 打开“Options for Target”对话框 - 选择“Target”选项卡,在“Code Generation”区域,确保未勾选“Use MicroLIB”(因为MicroLIB默认使用Semihosting,除非重定向) - 选择“C/C++”选项卡,在“Misc Controls”中添加`--no_semihosting` - 选择“Linker”选项卡,在“Misc controls”中添加`--no_semihosting` ### 步骤2:重定向标准库函数(可选但推荐) 即使关闭了Semihosting,如果你的程序使用了标准库函数(如`printf`、`scanf`),则需要提供这些函数的底层实现。否则,链接器会报错找不到这些函数。 你可以重定向标准库函数到你的硬件设备(如UART)。ARM Compiler提供了一组底层IO函数,你需要实现它们。这些函数通常包括: - `int _sys_open(const char *name, int openmode)` - `int _sys_close(int handle)` - `int _sys_write(int handle, const char *buffer, int size)` - `int _sys_read(int handle, char *buffer, int size)` - `int _sys_istty(int handle)` - `int _sys_seek(int handle, long pos)` - `long _sys_flen(int handle)` - `void _sys_exit(int status)` 例如,重定向`_sys_write`到UART: ```c #include <rt_misc.h> #include <stdio.h> // 假设UART发送一个字符的函数 void UART_SendChar(char c) { // 实现发送字符到UART的代码 } int _sys_write(int handle, const char *buffer, int size) { if (handle == 1) { // stdout for (int i = 0; i < size; i++) { UART_SendChar(buffer[i]); } return size; } return -1; // 错误 } ``` 注意:`handle`的值1通常代表标准输出(stdout),2代表标准错误(stderr)。 ### 步骤3:避免使用需要Semihosting的函数 确保你的代码中没有直接或间接使用依赖于Semihosting的函数。例如,避免使用`malloc`(因为默认的堆分配可能依赖Semihosting)文件操作函数(如`fopen`、`fread`等),除非你已经实现了重定向。 ### 步骤4:使用自定义的启动文件(可选) 有时,默认的启动文件(如`startup_ARMCM7.s`)可能包含Semihosting相关的设置。你可以修改启动文件,移除与Semihosting相关的部分,但通常使用`--no_semihosting`选项已经足够。 ### 注意事项 - 使用`--no_semihosting`选项后,链接器将不会链接Semihosting相关的库函数。 - 如果你使用MicroLIB(在Keil中),则需要注意MicroLIB默认使用Semihosting。因此,关闭Semihosting时,建议不使用MicroLIB,或者如果你使用MicroLIB,则必须重定向所有需要Semihosting的函数。在MicroLIB中,重定向是通过实现`__syscalls`函数来实现的,具体函数与上面列出的类似。 - 关闭Semihosting后,如果程序尝试进行Semihosting调用(如调用`printf`而没有重定向),则会产生运行时错误(如Undefined Instruction异常),因为Semihosting指令(SVC或BKPT)将不会被处理。 总结:在ARM A7上使用ARM Compiler 5.01关闭Semihosting,主要是在编译链接时添加`--no_semihosting`选项,并提供必要的底层IO重定向函数。 希望这些步骤能帮助你成功关闭Semihosting。 相关问题: 1. 如何重定向ARM Compiler的标准输入输出? 2. 在ARM Compiler中,MicroLIB标准C库在Semihosting方面有何区别? 3. 如何避免在ARM程序中使用Semihosting? 4. 当Semihosting被关闭时,如何调试ARM应用程序的输入输出? 引用说明: 根据引用[4],Semihosting只能使用在开发板调试主机通过仿真器连接的情况下,脱离主机调试环境则不能正常运行[^4]。因此,在产品发布时关闭Semihosting是必要的。 </think>### ARM A7 使用 ARM Compiler 5.01 关闭 Semihosting 的方法 在 ARM A7 处理器上使用 ARM Compiler 5.01(armcc)时,关闭 Semihosting 需要以下步骤: #### ✅ 方法 1:编译选项关闭(推荐) 在编译器链接器选项中添加 `--no_semihosting` 标志: ```bash armcc --no_semihosting -c source.c # 编译选项 armlink --no_semihosting object.o # 链接选项 ``` 这会使编译器移除所有 Semihosting 相关的库调用[^4]。 #### ✅ 方法 2:重定向标准库函数 实现自定义的底层 I/O 函数覆盖 Semihosting: ```c // 重定向_write函数(以串口输出为例) int _write(int fd, char *ptr, int len) { for(int i=0; i<len; i++) { UART_SendByte(*ptr++); // 自定义串口发送函数 } return len; } ``` 在分散加载文件中指定自定义库: ```scatter LIBS += my_nosemihost_library.a ``` #### ✅ 方法 3:修改启动文件 在汇编启动文件(如 `startup_ARMCA7.s`)中禁用 Semihosting 中断: ```assembly ; 注释掉 Semihosting 相关代码 ; BL __use_no_semihosting_swi ``` #### ✅ 方法 4:运行时检查 添加运行时检测防止意外调用: ```c // 在main函数起始处添加 extern void initialise_monitor_handles(void); #pragma import(__use_no_semihosting_swi) // 强制禁用[^4] int main(void) { #ifdef __SEMIHOSTING__ #error "Semihosting still enabled!" // 编译时检查 #endif // ...应用代码 } ``` #### 验证是否关闭成功 1. 反编译检查: ```bash fromelf --text -c output.axf > disassembly.txt ``` 搜索 `SVC` 或 `BKPT` 指令(Semihosting 调用指令)[^3] 2. 调试器测试: ```bash # 在调试命令行输入 semihosting disable # 在Keil/J-Link/GDB中执行 ``` > **关键原理**:Semihosting 通过 `SVC`(ARMv7)或 `BKPT`(Cortex-M)指令实现主机通信[^3],关闭后需提供替代 I/O 实现[^4]。 --- ### 相关问题 1. Semihosting 关闭后如何重定向 `printf` 到串口? 2. ARM Compiler 5 ARM Compiler 6 在 Semihosting 处理上有何差异? 3. Semihosting 对 ARM A7 实时性有何影响? 4. 如何检测代码中是否意外使用了 Semihosting 功能? 5. Semihosting 与 ITM(Instrumentation Trace Macrocell)有何替代关系? [^3]: ARMv7 处理器使用 SVC 指令实现 Semihosting 调用[^3] [^4]: Semihosting 只能用于调试环境,产品发布时必须关闭或重定向[^4]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值