原始的代码来自:【MoreWindows工作笔记5】StrFormatByteSize64 高端大气的显示文件大小
但是代码中有一个BUG,你看出什么问题来了吗?
// 【MoreWindows工作笔记5】StrFormatByteSize64 高端大气的显示文件大小
#include <stdio.h>
#include <math.h>
#include <shlwapi.h>
#pragma comment(lib, "shlwapi.lib")
void NormFileSize(LONGLONG file_length, char* buffer, int buffer_size) {
static double BASE_NUMBER = 1024.0;
static char* SIZE_NAME[] = {"B","KB", "MB", "GB", "TB", "PB"};
double length = static_cast<double>(file_length);
int i = 0;
while (i < ARRAYSIZE(SIZE_NAME) && length > BASE_NUMBER) {
length /= BASE_NUMBER;
i++;
}
sprintf(buffer, "%lf %s", length, SIZE_NAME[i]);
}
int main()
{
printf(" 【MoreWindows工作笔记5】StrFormatByteSize64 高端大气的显示文件大小\n");
printf(" - http://blog.youkuaiyun.com/morewindows/article/details/16358955 -\n");
printf(" -- By MoreWindows( http://blog.youkuaiyun.com/MoreWindows ) --\n\n");
LONGLONG file_length = 34578;
for (int i = 0; i < 10; i++)
{
printf("字节数 = %15I64d -- 文件大小:", file_length);
char file_length_str1[100] = {0};
NormFileSize(file_length, file_length_str1, 100);
printf("%15s ", file_length_str1);
WCHAR file_length_str2[100] = {0};
StrFormatByteSizeW(file_length, file_length_str2, 100); // StrFormatByteSizeW会截断尾数
printf("%ls\n", file_length_str2); // 还可以试试StrFormatKBSize,这个只用KB来表示
file_length *= 11;
}
return 0;
}
现在我们就来再现问题
首先有个疑问: LONGLONG 能表示的最大值是多少?
是9223372036854775807(具体看http://blog.youkuaiyun.com/wisepragma/article/details/23882069)
改动一下代码
将static char* SIZE_NAME[] = {"B","KB", "MB", "GB", "TB", "PB"}; 的static去掉
将9223372036854775807代入函数NormFileSize()
[文件为testprintf.cpp]
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <stdio.h>
#include <math.h>
#include <shlwapi.h>
#pragma comment(lib, "shlwapi.lib")
void NormFileSize(LONGLONG file_length, char* buffer, int buffer_size) {
static double BASE_NUMBER = 1024.0;
char* SIZE_NAME[] = {"B","KB", "MB", "GB", "TB", "PB"};
double length = static_cast<double>(file_length);
int i = 0;
while (i < ARRAYSIZE(SIZE_NAME) && length > BASE_NUMBER) {
length /= BASE_NUMBER;
i++;
}
sprintf(buffer, "%lf %s", length, SIZE_NAME[i]);
}
int main()
{
printf(" 【MoreWindows工作笔记5】StrFormatByteSize64 高端大气的显示文件大小\n");
printf(" - http://blog.youkuaiyun.com/morewindows/article/details/16358955 -\n");
printf(" -- By MoreWindows( http://blog.youkuaiyun.com/MoreWindows ) --\n\n");
LONGLONG file_length = 34578;
char file_length_str1[100] = {0};
WCHAR file_length_str2[100] = {0};
//for (int i = 0; i < 10; i++)
//{
// printf("字节数 = %15I64d -- 文件大小:", file_length);
// NormFileSize(file_length, file_length_str1, 100);
// printf("%15s ", file_length_str1);
// StrFormatByteSizeW(file_length, file_length_str2, 100); // StrFormatByteSizeW会截断尾数
// printf("%ls\n", file_length_str2); // 还可以试试StrFormatKBSize,这个只用KB来表示
// file_length *= 11;
//}
file_length=9223372036854775807;
NormFileSize(file_length, file_length_str1, 100);
return 0;
}
运行后就会出现
接着我们要来抓鬼--为什么会出现这个问题呢?
在下面所示的这个循环内,i值一直都是合法的,而且语义上完全没有问题,
while (i < ARRAYSIZE(SIZE_NAME) && length > BASE_NUMBER)
{
length /= BASE_NUMBER;
i++;
}
但是循环下面的代码引用了这个变量,一般情况下,不会有问题,然后有人在问---我们一定要把没问题的东西测出问题来吗?但是如果在极端情况下,比如代入很大的值,于是退出循环后i==ARRAYSIZE(SIZE_NAME),我们知道,C++的数组const int MAX_INDEX=1000;int array[MAX_INDEX]下标范围是0~MAX_INDEX-1,元素为array[0],array[1],...array[MAX_INDEX-1],使用array[MAX_INDEX]就是越界!!!解决办法就是在越界前结束循环:
while (i < ARRAYSIZE(SIZE_NAME)-1 && length > BASE_NUMBER)
{
length /= BASE_NUMBER;
i++;
}
附上完整函数进化版本
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
TCHAR* Bytes2StringEx(const ULARGE_INTEGER ullSizeInBytes,TCHAR *lpszSizeInfo)//StrFormatByteSize64在ANSI工作不正常
{
//from:MoreWindows <<StrFormatByteSize64 高端大气的显示文件大小>>
//bytes
//bytes/1024 kB
//bytes/1024/1024 MB
//bytes/1024/1024/1024 GB
//bytes/1024/1024/1024/1024 TB
long double FileSize= static_cast<long double>(ullSizeInBytes.QuadPart);
long double BASE_NUMBER = 1024.0;
TCHAR *UnitName[] = {TEXT("B"),TEXT("kB"), TEXT("MB"),TEXT("GB"), TEXT("TB"),TEXT("PB")};
int i = 0;
int max_index=sizeof(UnitName)/sizeof(UnitName[0]) ; //int max_index2=ARRAYSIZE(UnitName);
while ( i<max_index-1 && FileSize>1024.0 )
{
FileSize /= 1024.0;
i++;
}
TCHAR *un=UnitName[i];//虽然上面的while中i不会超出max_index但是,当超出时,下面引用到的正是超出的这个下标
#pragma warning(disable:4996)
_sntprintf(lpszSizeInfo,10230, TEXT("%lf %s"),FileSize, un);
return lpszSizeInfo;
}
TCHAR* Bytes2StringEx2(const ULARGE_INTEGER ullSizeInBytes,TCHAR *lpszSizeInfo)//StrFormatByteSize64在ANSI工作不正常
{
//from:MoreWindows <<StrFormatByteSize64 高端大气的显示文件大小>>
// http://blog.youkuaiyun.com/morewindows/article/details/16358955#comments
// ulx.QuadPart=14757395258967641292;//代入这个天文数字,然后已经不是什么PB
//bytes
//bytes/1024 kB
//bytes/1024/1024 MB
//bytes/1024/1024/1024 GB
//bytes/1024/1024/1024/1024 TB
long double FileSize= static_cast<long double>(ullSizeInBytes.QuadPart);
TCHAR *StorageUnit[] = {TEXT("B"),TEXT("kB"), TEXT("MB"),TEXT("GB"),TEXT("TB"),TEXT("PB"),TEXT("EB"),TEXT("ZB"),TEXT("YB"),TEXT("BB"),TEXT("NB"),TEXT("DB")};
const int UNIT_MAX_INDEX=sizeof(StorageUnit)/sizeof(StorageUnit[0]);
const long double BASE_NUMBER = 1024.0;
int i = 0;
while ( i<UNIT_MAX_INDEX-1 && FileSize>BASE_NUMBER )//bug fixed:虽然上面的while中i不会超出UNIT_MAX_INDEX但是,当超出时,下面引用到的正是超出的这个下标所以要减一:i<UNIT_MAX_INDEX-1
{
FileSize /=BASE_NUMBER;
i++;
}
#pragma warning(disable:4996)
_stprintf(lpszSizeInfo,TEXT("%lf%s"),FileSize, StorageUnit[i]);
return lpszSizeInfo;
}
int _tmain()
{
ULARGE_INTEGER ulx;
ulx.QuadPart=-1;//(unsigned long long)-1==18446744073709551615,0xcccccccccccccccc=14757395258967641292;//代入这个天文数字,然后已经不是什么PB
TCHAR lpszSizeInfo[20];
_tprintf(TEXT("%I64u bytes"),ulx.QuadPart );
Bytes2StringEx(ulx,lpszSizeInfo);
_tprintf(TEXT("=%s "),lpszSizeInfo);
Bytes2StringEx2(ulx,lpszSizeInfo);
_tprintf(TEXT("=%s\n"),lpszSizeInfo);
getchar();//pause
return 0;
}