C语言中不同数据类型在内存中的存储格式研究
学了这么多年C语言、C++、VC、MFC,但却从来没有认真研究过各种数据类型在内存中是如何存储的。感觉自己一直在弄的都是皮毛,没有触及真正核心的东西。直到昨天,重新翻看谭浩强老师经典的《C程序设计(第三版)》,在“第十四章 常见错误和程序调试”中有一个例子是这样的:
1 int a=3; 2 float b=4.5; 3 printf("%f %d",a,b);
输出的结果是这样的:
为什么会是这样的结果呢?让我们看一看a和b在内存中的存储方式吧?
int 和 long 一样,按 2 的补码、低位字节在前的形式存储于 4 个字节中;
float 按 IEEE 754 单精度数的形式存储于 4 个字节中;
double 按 IEEE 754 双精度数的形式存储于 8 个字节中。
a是int型的,在内存中占4个字节,在内存中的存储方式:
地址:0x0012ff7c 0x0012ff7d 0x0012ff7e 0x0012ff7f
数值: 03 00 00 00
b是float型的,在内存中占4个字节,在内存中的存储方式:
地址:0x0012ff70 0x0012ff71 0x0012ff72 0x0012ff73
数值: 00 00 90 40
看了printf的源码:
1 int printf( char * format , ...) 2 { 3 va_list ap; 4 int n; 5 va_start (ap,format); 6 n=vprintf(format ,ap); 7 va_end(ap); 8 return n; 9 }
又看了vprintf的源码:
1 /*** 2 *vprintf.c - printf from a var args pointer 3 * 4 * Copyright (c) Microsoft Corporation. All rights reserved. 5 * 6 *Purpose: 7 * defines vprintf() - print formatted data from an argument list pointer 8 * 9 *******************************************************************************/ 10 11 #include <cruntime.h> 12 #include <stdio.h> 13 #include <dbgint.h> 14 #include <stdarg.h> 15 #include <internal.h> 16 #include <file2.h> 17 #include <mtdll.h> 18 #include <stddef.h> 19 20 /*** 21 *int vprintf(format, ap) - print formatted data from an argument list pointer 22 * 23 *Purpose: 24 * Prints formatted data items to stdout. Uses a pointer to a 25 * variable length list of arguments instead of an argument list. 26 * 27 *Entry: 28 * char *format - format string, describes data format to write 29 * va_list ap - pointer to variable length arg list 30 * 31 *Exit: 32 * returns number of characters written 33 * 34 *Exceptions: 35 * 36 *******************************************************************************/ 37 38 int __cdecl vprintf_helper ( 39 OUTPUTFN outfn, 40 const char *format, 41 _locale_t plocinfo, 42 va_list ap 43 ) 44 /* 45 * stdout 'V'ariable, 'PRINT', 'F'ormatted 46 */ 47 { 48 REG1 FILE *stream = stdout; 49 REG2 int buffing; 50 REG3 int retval; 51 52 _VALIDATE_RETURN( (format != NULL), EINVAL, -1); 53 54 55 _lock_str(stream); 56 __try { 57 58 buffing = _stbuf(stream); 59 retval = outfn(stream, format, plocinfo, ap ); 60 _ftbuf(buffing, stream); 61 62 } 63 __finally { 64 _unlock_str(stream); 65 } 66 67 return(retval); 68 } 69 70 int __cdecl _vprintf_l ( 71 const char *format, 72 _locale_t plocinfo, 73 va_list ap 74 ) 75 { 76 return vprintf_helper(_output_l,format, plocinfo, ap); 77 } 78 79 int __cdecl _vprintf_s_l ( 80 const char *format, 81 _locale_t plocinfo, 82 va_list ap 83 ) 84 { 85 return vprintf_helper(_output_s_l,format, plocinfo, ap); 86 } 87 88 int __cdecl _vprintf_p_l ( 89 const char *format, 90 _locale_t plocinfo, 91 va_list ap 92 ) 93 { 94 return vprintf_helper(_output_p_l,format, plocinfo, ap); 95 } 96 97 int __cdecl vprintf ( 98 const char *format, 99 va_list ap 100 ) 101 { 102 return vprintf_helper(_output_l,format, NULL, ap); 103 } 104 105 int __cdecl vprintf_s ( 106 const char *format, 107 va_list ap 108 ) 109 { 110 return vprintf_helper(_output_s_l,format, NULL, ap); 111 } 112 113 int __cdecl _vprintf_p ( 114 const char *format, 115 va_list ap 116 ) 117 { 118 return vprintf_helper(_output_p_l,format, NULL, ap); 119 }
发现里面使用了可变参数,即函数根本不知道传进来的参数类型,所以可能出现匹配错误的问题。4.5如果按照double类型存储格式是:
0x4012000000000000,按照%d格式输出就是1074921472,与我们前面得到的结果正好相同。
下面,再做一个实验:
1 int a=3; 2 int *p=&a; 3 double b=4.5; 4 double *q=&b; 5 printf("%f %d",a,b);
运行结果如下:
与前面得到的结果完全相同。
看来问题的原因在于可变参数类别表,传入的参数类型与编译器的解释类型不一致引起的,所以以后编程时一定要慎用可变参数列表。在网上看到一篇文章也提到了使用可变参数列表应该注意的问题:
(1)可变参数的类型和个数完全由程序代码控制,它并不能智能地识别不同参数的个数和类型;
(2)如果我们不需要一一详解每个参数,只需要将可变列表拷贝至某个缓冲,可用vsprintf函数;
(3)因为编译器对可变参数的函数的原型检查不够严格,对编程查错不利.不利于我们写出高质量的代码;