来源于项目中一个真实的bug,最近项目中要适配32位linux系统时发现一段检测磁盘剩余空间的程序,低于一定空间的话退出安装程序安装失败.
代码简化如下:
参考如下代码:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/vfs.h>
#include <errno.h>
#include <stdint.h>
#include <limits.h>
int main()
{
struct statfs diskInfo;
/*调试时指定一个存在的目录*/
char *pszInstallDir="/root";
memset(&diskInfo, 0, sizeof(struct statfs));
/*1500MB*/
uint64_t uByteSize = 1500*1024*1024;
if (statfs(pszInstallDir, &diskInfo) != 0) {
return -1;
}
long m = diskInfo.f_bfree * diskInfo.f_bsize;
printf("m = %ld LONG_MAX = %ld\n",m,LONG_MAX);
printf("%ld %ld\n", diskInfo.f_bfree , diskInfo.f_bsize);
if ((diskInfo.f_bfree * diskInfo.f_bsize) < uByteSize)
{
errno = ENOSPC;
printf("check disk free space (%s) failed with error code = %d",
pszInstallDir,
errno);
return -1;
}
return 0;
}
执行结果:
[root@localhost ~]# ./statfs
m = 119513088 LONG_MAX = 2147483647
12612090 4096
明显可以看出long最大为2147483647,实际结果为51659120640,超过最大值,发生溢出,因此在下面的代码中可以将结构体diskInfo中的字段保存在uint64_t中进行计算,可以确保在合理值范围内不会发生溢出。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/vfs.h>
#include <errno.h>
#include <stdint.h>
#include <limits.h>
int main()
{
struct statfs diskInfo;
/*调试时指定一个存在的目录*/
char *pszInstallDir="/root";
memset(&diskInfo, 0, sizeof(struct statfs));
/*1500MB*/
uint64_t uByteSize = 1500*1024*1024;
if (statfs(pszInstallDir, &diskInfo) != 0) {
return -1;
}
long m = diskInfo.f_bfree * diskInfo.f_bsize;
printf("m = %ld LONG_MAX = %ld\n",m,LONG_MAX);
uint64_t f_bfree = diskInfo.f_bfree;
uint64_t f_bsize = diskInfo.f_bsize;
uint64_t totalfree = f_bfree * f_bsize;
printf("correct = %llu\n",totalfree);
if (totalfree < uByteSize)
{
errno = ENOSPC;
printf("check disk free space (%s) failed with error code = %d",
pszInstallDir,
errno);
return -1;
}
return 0;
}
执行结果:
[root@localhost ~]# ./statfs
m = 119513088 LONG_MAX = 2147483647
correct = 51659120640
以上程序中结构体diskInfo中的f_bfree和f_bsize均位long类型,在32位系统中long为int,我们可以在头文件中看到32位系统中long类型能表示的最大值为2147483647,当我们的计算结果超过这个值时将发生溢出,结果不可信。
系统库目录 /usr/include/limits.h
/* Minimum and maximum values a `signed long int' can hold. */
# if __WORDSIZE == 64
# define LONG_MAX 9223372036854775807L
# else
# define LONG_MAX 2147483647L
# endif
# define LONG_MIN (-LONG_MAX - 1L)
我们可以使用uint64_t可以避免溢出,参考/usr/include/stdint.h
/* Unsigned. */
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
#ifndef __uint32_t_defined
typedef unsigned int uint32_t;
# define __uint32_t_defined
#endif
#if __WORDSIZE == 64
typedef unsigned long int uint64_t;
#else
__extension__
typedef unsigned long long int uint64_t;
#endif
__extension__是gcc对标准c语言的扩展,使用这些扩展功能时,编译器会发提出警告,使用__extension__关键字会告诉gcc不要提出警告.使用stdint.h中类型可以增强程序的可移植性。
需要在异构平台上传递的结构,不直接使用随平台不一样而导致长度不一的数据类型,主要指:long,unsigned long;可使用在不同平台保持长度一致的各种类型别名,如:int32_t/int64_t,U32/I32/U64/I64,WORD/DWORD等。