【题外话】

以前用HUSTOJ给学校搭建Online Judge,所有的评测都是在Linux下进行的。后来为了好往学校服务器上部署,所以大家重新做了一套Online Judge,Web和Judge都是基于Windows和.NET平台的。这两天将学校Online Judge中以前在Linux下(GCC 4.6.3)评测的提交全部在Windows上(GCC 4.7.2 MinGW)重测一遍,结果莫名其妙发现很多以前通过的题目现在出现了结果错误的问题,其共同结果都是结果为0,查看源代码发现其都是使用printf("%ld")输出的double。原本以为是GCC的Bug,后来查找资料才发现实际上是对C语言了解不够充分加上MinGW的问题才共同导致的问题。

【文章索引】

  1. 奇怪的问题
  2. 格式化字符串的问题
  3. MinGW的问题

【一、奇怪的问题】

把出错的一个提交的代码精简,然后就剩下如下的代码:

#include <cstdio>
using namespace std;
int main()
{
double n;
scanf("%lf",&n);
printf("%.0lf\n",n);
return ;
}

在Linux下结果正常:

结果在Windows下会出现如下图的结果:

【二、格式化字符串的问题】

接下来将上述程序的printf替换为cout,发现没有任何问题,判断是printf那行出现了问题。

查找相关资料(如相关链接1)发现,不论输出float还是double都应该使用printf("%f"),因为不论float还是double都会作为double类型输出,确实以前没有注意到这个问题。所以在第一节给出的那个程序将“%lf”改为“%f”就正确了。但相关链接1中并没有说明%lf指的是什么。

不过如果尝试在GCC上编译如下的代码却会给出如下图的警告:

#include <cstdio>
using namespace std; int main()
{
long double n = 1.22222222;
printf("%f", n);
printf("%lf", n);
return ;
}

也就是说,对于GCC而言,在printf中使用“%f”和“%lf”实际上都表示的是double类型,而要表示long double,则应该使用“%Lf”(注意大小写),而使用MSVC编译编译时并没有发生这些问题(也可能是因为MSVC认为double = long double,所以一切都一样了吧)。

【三、MinGW的问题】

虽然上一节找出了第一节程序的问题,可是为什么会出现这样的问题呢。

继续查找发现了相关链接2和相关链接3,发现在这两个问题的回答中都提到了MinGW在Windows上运行是需要MSVC的运行时的。以前确实也没注意到这点,于是去MinGW的官方网站,确实发现了如下两段:

MinGW provides a complete Open Source programming tool set which is suitable for the development of native MS-Windows applications, and which do not depend on any 3rd-party C-Runtime DLLs. (It does depend on a number of DLLs provided by Microsoft themselves, as components of the operating system; most notable among these is MSVCRT.DLL, the Microsoft C runtime library. Additionally, threaded applications must ship with a freely distributable thread support DLL, provided as part of MinGW itself).

MinGW compilers provide access to the functionality of the Microsoft C runtime and some language-specific runtimes. MinGW, being Minimalist, does not, and never will, attempt to provide a POSIX runtime environment for POSIX application deployment on MS-Windows. If you want POSIX application deployment on this platform, please consider Cygwin instead.

果然,MinGW虽然不需要任何第三方的运行库,但是需要微软的运行库,其中包括了MSVCRT.DLL以及其他的微软C语言运行库。所以GCC编译后的程序还是运行在MSVC运行库上的程序。同时又由于32位的MSVC并不支持更高精度的double类型(在32位的MSVC中long double与double的精度均为8位,见相关链接4),而GCC在32位的long double是12字节,64位更是16字节,所以就出现了不兼容的问题。

所以我们可以做这样一个实验,将long double类型存储的数据按字节输出、同时按不同方式输出其结果,代码如下:

 #include <cstdio>
using namespace std; void print_bytes(const char* name, long double &n)
{
char* p = (char*)&n; printf("%s [%ld-%ld]\n", name, p, p + sizeof(long double)); for (int i = ; i < sizeof(long double); i++)
{
printf("0x%02X ", (*p & 0xFF));
p++;
} printf("\n");
} int main()
{
long double n1 = ;
long double n2 = ; print_bytes("inited_n1", n1);
print_bytes("inited_n2", n2);
printf("\n"); scanf("%lf", &n1);
scanf("%Lf", &n2); print_bytes("inputed_n1", n1);
print_bytes("inputed_n2", n2);
printf("\n"); printf("type \t\t n1 \t\t\t\t n2\n");
printf("%%f \t\t ");
printf("%f \t\t\t ", n1);
printf("%f\n", n2); printf("%%lf \t\t ");
printf("%lf \t\t\t ", n1);
printf("%lf\n", n2); printf("%%Lf \t\t ");
printf("%Lf \t\t\t ", n1);
printf("%Lf\n", n2); return ;
}

分别将这个代码在32位机器上用MSVC和GCC MinGW编译,以及在32位Linux下用GCC编译,可以得到如下的结果(从上到下分别为32位Windows下用MSVC编译、32位Windows下用GCC MinGW编译、32位Linux下用GCC编译):

可以发现,MSVC下long double为8字节,而GCC编译后的程序不论在Linux下还是在Windows下都为12字节。不过仔细看可以发现,虽然GCC生成的程序占用了12字节,但其只用到了前10字节(后2字节不论怎样赋值其内容都不会发生改变),也就是说GCC的long double实际上是10字节(80bit)的。

除此之外,还可以发现,当使用scanf("%lf")时,不论变量是什么类型的,都是按8字节存储的(即按double类型存储的),而使用scanf("%Lf"),则是按10字节存储的(即按long double类型存储的)。由于在MSVC下double = long double,所以不论怎么混用,结果都是正确的。而在Linux下,我们发现,当存储的long double为真正的long double时(使用scanf("%Lf")),只能使用%Lf输出结果,而long double内存储的内容为double时,只能使用输出double的格式化字符串输出。

所以猜想在GCC MinGW下,可能就像在Linux下存储的double而强制输出long double那样会输出为0一样,存储的内容为double,而MSVC将其认定为long double输出,所以最终结果为0。

【相关链接】

  1. 为什么printf()用%f输出double型,而scanf却用%lf呢?:http://book.51cto.com/art/200901/106880.htm
  2. printf and long double:http://stackoverflow.com/questions/4089174/printf-and-long-double
  3. gcc: printf and long double leads to wrong output:http://stackoverflow.com/questions/7134547/gcc-printf-and-long-double-leads-to-wrong-output-c-type-conversion-messes-u
  4. Long Double:http://msdn.microsoft.com/en-us/library/9cx8xs15.aspx

关于printf错用格式化字符串导致double和long double输出错误的小随笔的更多相关文章

  1. Linux pwn入门教程(6)——格式化字符串漏洞

    作者:Tangerine@SAINTSEC 0x00 printf函数中的漏洞 printf函数族是一个在C编程中比较常用的函数族.通常来说,我们会使用printf([格式化字符串],参数)的形式来进 ...

  2. Linux pwn入门教程——格式化字符串漏洞

    本文作者:Tangerine@SAINTSEC 原文来自:https://bbs.ichunqiu.com/thread-42943-1-1.html 0×00 printf函数中的漏洞printf函 ...

  3. CTF必备技能丨Linux Pwn入门教程——格式化字符串漏洞

    Linux Pwn入门教程系列分享如约而至,本套课程是作者依据i春秋Pwn入门课程中的技术分类,并结合近几年赛事中出现的题目和文章整理出一份相对完整的Linux Pwn教程. 教程仅针对i386/am ...

  4. 详谈Format String(格式化字符串)漏洞

    格式化字符串漏洞由于目前编译器的默认禁止敏感格式控制符,而且容易通过代码审计中发现,所以此类漏洞极少出现,一直没有笔者本人的引起重视.最近捣鼓pwn题,遇上了不少,决定好好总结了一下. 格式化字符串漏 ...

  5. Python格式化字符串知多少

    字符串格式化相当于字符串模板.也就是说,如果一个字符串有一部分是固定的,而另一部分是动态变化的,那么就可以将固定的部分做成模板,然后那些动态变化的部分使用字符串格式化操作符(%) 替换.如一句问候语: ...

  6. [典型漏洞分享]YS VTM模块存在格式化字符串漏洞,可导致VTM进程异常退出【高危】

    YS VTM模块存在格式化字符串漏洞,可导致VTM进程异常退出[高危] 问题描述: YS VTM模块开放对外监听端口(8554和8664),此次使用sulley fuzzing框架对监听在8664端口 ...

  7. [二进制漏洞]PWN学习之格式化字符串漏洞 Linux篇

    目录 [二进制漏洞]PWN学习之格式化字符串漏洞 Linux篇 格式化输出函数 printf函数族功能介绍 printf参数 type(类型) flags(标志) number(宽度) precisi ...

  8. C语言printf()函数:格式化输出函数

    C语言printf()函数:格式化输出函数 头文件:#include <stdio.h> printf()函数是最常用的格式化输出函数,其原型为:     int printf( char ...

  9. sprintf格式化字符串带来的注入隐患

    原文链接:https://paper.seebug.org/386/ 摘要点关键知识点 <?php $input = addslashes("%1$' and 1=1#"); ...

随机推荐

  1. Knockout.js随手记(7)

    数组元素的新增/移除事件 前两篇博客已经很清楚的知道knockout.js通过observableArray()数组元素增减,可以实时的反映在UI上.当然我们想在数组增加或移除元素时加上自定义逻辑就好 ...

  2. Swift 基本常量和变量,基本数据类型

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px Menlo; color: #4dbf56 } p.p2 { margin: 0.0px 0. ...

  3. 学习微信小程序之css4设置颜色,单位表示,字体样式

    颜色的设置可以通过RGB设置 可以直接通过英文单词设置 可以通过16进制来设置 长度单位: 字体样式: 设置字体样式 字体粗细 设置字体风格 设置字间距

  4. h5 notes

    HTML5 服务器发送事件(Server-Sent Events)

  5. npm 基础

    npm账户 npm adduser npm whoami 初始化项目: npm init --scope=<username> 项目必要文件 README.md pageage.json: ...

  6. [spring源码学习]八、IOC源码-messageSource

    一.代码实例 我们在第八章可以看到,spring的context在初始化的时候,会默认调用系统中的各种约定好的bean,其中第一个bean就是id为messageSource的bean,我们了解这应该 ...

  7. 记录同事的一个bug-ajax-413错误-fullhead

    症状表现为在form下面的textarea里的字符数只有几十个的时候,请求可以成功,但是如果有几百字,则会出现413错误,提示fullhead,我第一反应是cookie体积太小,但是清了缓存还是一样的 ...

  8. solr DIH 知识梳理

    solr DIH 知识梳理 web.xml中listener配置 <listener> <listener-class>org.apache.solr.handler.data ...

  9. [Noip2016]蚯蚓 D2 T2 队列

    [Noip2016]蚯蚓 D2 T2 Description 本题中,我们将用符号[c]表示对c向下取整,例如:[3.0」= [3.1」=[3.9」=3.蛐蛐国最近蚯蚓成灾了!隔壁跳 蚤国的跳蚤也拿蚯 ...

  10. 洛谷 P1827 美国血统 American Heritage Label:字符串Water

    题目描述 农夫约翰非常认真地对待他的奶牛们的血统.然而他不是一个真正优秀的记帐员.他把他的奶牛 们的家谱作成二叉树,并且把二叉树以更线性的“树的中序遍历”和“树的前序遍历”的符号加以记录而 不是用图形 ...