c语言中的long占位符(C按位字节级)
构成电子计算机基本逻辑单元的晶体管可以表示两种状态,用二进制描述就是0或1,称为一个二进制位(bit),多个晶体管的组合可以实现逻辑电路,数据和指令都可以以二进制的序列来表示。
通常以8个二进制位组成一个字节(byte),以字节为单位进行编址。
CPU在单位时间内能一次处理的二进制数的位数叫字长(word size)。字长指明指针数据的标称大小(nominal size)。对于一个字长为n位的机器而言,虚拟地址的范围为0-2^n-1,程序最多可以访问2^n个字节。如32位机器的虚拟地址范围为0x~0xFFFFFFFF。32位CPU表示该CPU处理的字长为32位,即一次处理4个字节。32位操作系统表示支持32位的CPU。64位CPU —指的是该CPU处理的字长为64位,即一次处理8个字节。64位操作系统表示支持64位的CPU,操作系统通常向后兼容,所以也支持32位操作系统。
使用PAE36技术的32位CPU是36根地址线,使用PAE40技术的Intel x86-64 CPU是40根地址线,使用PAE52技术的AMD x86-64 CPU是52根地址线。
C语言支持位级(bitwise)操作,其数据类型类型有一个字节长度的char类型,一个字长的int类型,指针本身的存储也使用一个字长。
1 位级(bitwise)处理C语言支持按位与、或、非操作,也支持移位操作。位操作的一个显著用途就是节省存储空间。
如在公历转农历的程序中,可以使用一个unsigned int来存储一个年份中的诸多信息:
如2019年的信息用0x0A9345表示,其二进制位为0000 1010 1001 00110 10 00101。
(16进制解析,每一个16进制位(0-f)是4个二进制位:0000-1111)
20-23位,其十进制值表示闰月月份,值为0 表示无闰月(第23位代表最高位,最左边)
7到19位,分别代表农历每月(在闰年有13个月)的大小,每一位代表一个月份。
(1表示大月为30天,0表示小月29天)
5到6位,其十进制值表示春节所在公历月份(此处是2月)
0到4位,其十进制值表示春节所在公历日期(此处是5日)
解析出不同的位组便可以得到该年不同的信息。
不同的年份可以存储到一个数组中:
unsigned int LunarCalendarTable[199] =
{
0x069349,0x7729BD,0x06AA51,0x0AD546,0x54DABA,0x04B64E,0x0A5743,0x452738,0x0D264A,0x8E933E,/*2081-2090*/
0x0D5252,0x0DAA47,0x66B53B,0x056D4F,0x04AE45,0x4A4EB9,0x0A4D4C,0x0D1541,0x2D92B5 /*2091-2099*/
};
点阵数字和字符信息也可以用字符数组存储和处理:
/*
输出点阵数字:8个char即可保存64个位的数据,例如3:
a[3]({0x00,0x1e,0x30,0x30,0x1c,0x30,0x30,0x1e}, //3
包括有8个十六进制的数,每行一个十六进制数,并且换成二进制的表示,会是什么样的呢?
00000000 //0x00
00011110 //0x1e
00110000 //0x30
00110000 //0x30
00011100 //0x1c
00110000 //0x30
00110000 //0x30
00011110 //0x1e
请看1出现的地方,可以借着鼠标按1出现的轨迹跟着划一划,不就是 数字3字型的轮廓吗?
只不过,耳朵状的3是反着的(这自有道理,看完程序1自会明白)。
————————————————
*/
#include <iostream>
using namespace std;
char a[10][8]=
{
{0x00,0x18,0x24,0x24,0x24,0x24,0x24,0x18}, //0
{0x00,0x18,0x1c,0x18,0x18,0x18,0x18,0x18}, //1
{0x00,0x1e,0x30,0x30,0x1c,0x06,0x06,0x3e}, //2
{0x00,0x1e,0x30,0x30,0x1c,0x30,0x30,0x1e}, //3
{0x00,0x30,0x38,0x34,0x32,0x3e,0x30,0x30}, //4
{0x00,0x1e,0x02,0x1e,0x30,0x30,0x30,0x1e}, //5
{0x00,0x1c,0x06,0x1e,0x36,0x36,0x36,0x1c}, //6
{0x00,0x3f,0x30,0x18,0x18,0x0c,0x0c,0x0c}, //7
{0x00,0x1c,0x36,0x36,0x1c,0x36,0x36,0x1c}, //8
{0x00,0x1c,0x36,0x36,0x36,0x3c,0x30,0x1c}, //9
};
int main()
{
int n=0,i,j,k,m,x;
cout<<"请输入需要显示的数字:";
int c[8];
cin>>n;
for(k=0; n&&k<8; k ) //c数组将分离出n中的各位数,不过是倒着的,例n=123,c中保存3 2 1
{
c[k]=n;
n/=10;
} //循环结束,将由k记住n是几位数,此处限最多8位数
for(i=0; i<8; i ) //一共要显示8行,不是依次显示k个数字,而是依次显示k个数字中对应的每一行
{
for(m=k-1; m>=0; m--) //要显示n=123, c中是倒着保存各位数的,所以m由大到小
{
x=a[c[m]][i]; //现在要显示的数字是c[m],所以取a数组中的第c[m]行,第i列数据
for(j=0; j<8; j )
{
if(x%2)
cout<<'*';
else
cout<<' ';
x=x/2;
}
}
cout<<endl;
}
while(1);
return 0;
}
/*
请输入需要显示的数字:68
*** ***
** ** **
**** ** **
** ** ***
** ** ** **
** ** ** **
*** ***
*/
浮点编码可以通过位域来解析:
#include <stdio.h>
void floatNumber_1(){
struct FF{ // 小端模式模拟double类型编码
unsigned l:32; // 剩下的小数位
unsigned m:15; // 剩下的小数位
unsigned k:5; // 取5位小数
unsigned j:11; // 阶码
unsigned i:1; // 符号位
};
union UN
{
double dd;
FF ff;
};
UN un;
un.dd = -15.75; // -1111.11
printf("%d\n",un.ff.i); // 1
printf("%d\n",un.ff.j); // 1023 3
printf("%d\n",un.ff.k); // 31 也就是二进制的11111
}
void floatNumber_2(){
struct FF{ // 小端模式模拟double类型编码
unsigned l:32; // 剩下的小数位
unsigned m:15; // 剩下的小数位
unsigned k:5; // 取5位小数
unsigned j:11; // 阶码
unsigned i:1; // 符号位
};
union UN
{
double dd;
FF ff;
};
UN un;
un.ff.i = 1;
un.ff.j = 1023 3;
un.ff.k = 31; // 二进制的11111
un.ff.m = 0;
un.ff.l = 0;
printf("%.2lf\n",un.dd); //un.dd = -15.75;// -1111.11
}
int main()
{
floatNumber_1();
floatNumber_2();
while(1);
return 0;
}
/*
1
1026
31
-15.75
*/
位域、共用体和可以解析汉字的GBK或GB2312编码:
void cngb()
{
union{
struct {
unsigned int i:4;
unsigned int j:4;
unsigned int k:4;
unsigned int L:4;
unsigned int m:4;
unsigned int n:4;
};
char hanzi[3];
}hz;
fflush(stdin);
puts("查询gb2312码,请输入一个汉字:");
gets(hz.hanzi);
//strcpy(hz.hanzi,"中");
printf("%X%X%X%X\n",hz.j,hz.i,hz.L,hz.k);
}
典型类型:char,char的长度是一个字节。
<limits.h>定义了一个宏:CHAR_BIT。
typedef unsigned char byte;
显示double的字节编码:
void showBytes(unsigned char* start, int len)
{
for(int i=0;i<len;i )
printf(" %.2x",start[i]);
printf("\n");
}
void showDoubleByte(double x)
{
showBytes((unsigned char*)&x,sizeof(double));
}
void showBytesByBig(unsigned char* start, int len)
{
unsigned int i = 0x00000001;
unsigned char* c = (unsigned char*)&i;
if(*c==1)// 小端返回true,大端返回0
{
for(int i=len-1;i>=0;i--)
printf(" %.2x",start[i]);
printf("\n");
}
}
memmove()函数实现:
memmove()由src所指定的内存区域赋值count个字符到dst所指定的内存区域。 src和dst所指内存区域可以重叠,但复制后src的内容会被更改。函数返回指向dst的指针。
void * my_memmove(void * dst,const void * src,int count)
{
void * ret = dst;
if(dst <= src || (char *)dst >= ((char *)src count))
{
while(count--)
{
*(char *)dst = *(char *)src;
dst = (char *)dst 1;
src = (char *)src 1;
}
}
else
{
dst = (char *)dst count - 1;
src = (char *)src count - 1;
while(count--)
{
*(char *)dst = *(char *)src;
dst = (char *)dst - 1;
src = (char *)src - 1;
}
}
return(ret);
}
int main()
{
char a[12];
puts((char *)my_memmove(a,"ammana_babi",16));
system("pause");
return 0;
}
二进制文件浏览:
#include<iostream>
#include<iomanip>
#include <fstream>
#include<cstdlib>
using namespace std;
int main( )
{
char c[16];
char f[100];
cout<<"请输入文件名:";
cin>>f;
ifstream infile(f,ios::in|ios::binary);
if(!infile)
{
cerr<<"open error!";
exit(1);
}
while(!infile.eof())
{
infile.read(c,16);
if(!infile.eof())
{
for(int i=0; i<16; i)
cout<<setfill('0')<<setw(2)<<hex<<int((unsigned char)(c[i]))<<" ";
cout<<'\t';
for(int i=0; i<16; i)
cout<<(c[i]?c[i]:'.');
cout<<endl;
}
}
return 0;
}
位级与字节级结合处理的实例:
考虑用一个short类型存储一个有效日期(年份取末两位):
/*
考虑用一个short类型存储一个有效日期(年份取末两位):
Year(0-99) 7 bits
Month(1-12)4 bits
Day(l-31) 5 bits
如2021/11/22
1234567890123456
0000000000000000
0000000000010101 // year左移9位留下7位有效位
0000000000001011 // Month左移5位留给Day
0000000000010110
*/
// 向整数中压缩数据
#include <iostream>
#include <iomanip>
using namespace std;
unsigned short dateShort(short year,short mon,short day)
{
unsigned short date;
date = (year << 16-7) | (mon << 5) | day;
return date;
}
void datePrint(unsigned short date)
{
struct Date{
unsigned day:5;
unsigned mon:4;
unsigned year:7;
};
Date *d = (Date*)&date;
printf("20%d/%d/%d",d->year,d->mon,d->day);
}
int main()
{
unsigned short date = dateShort(21,11,22);
datePrint(date); // 2021/12/22
getchar();
return 0;
}
按16进制显示数据:
#include <stdio.h>
void hexPrint(int n)
{
if(n==0)
printf("00 ");
char str[5] = {0};
int len = 0;
while(n)
{
int m = n;
n /= 16;
if(m<10)
str[len ] = m '0';
else
str[len ] = m 'A'-10;
}
--len;
if(len%2==0)
printf("0");
while(len>=0)
{
printf("%c",str[len--]);
if(len%2)
printf(" ");
}
}
hexPrint2(int n)
{
char str[5] = {0};
sprintf(str,"%X",n);
printf("%s ",str);
}
void bitsPrint(void *type,unsigned size)
{
unsigned char*p = (unsigned char*)type;
int endian = 1;
if(*(char*)&endian)
printf("小端字节序:");
for(unsigned i=0;i<size;i )
//printf("%d ",*p );
hexPrint(*p );
//hexPrint2(*p );
printf("\n");
}
int main()
{
int a = -123456789;
bitsPrint((void*)&a,sizeof a); // 小端字节序:EB 32 A4 F8
double b = -15.75; //
bitsPrint((void*)&b,sizeof b); // 小端字节序:00 00 00 00 00 80 2F C0
getchar();
return 0;
}
典型类型:int,int的长度是一个字长,32位CPU或操作系统是4个字节,64位是8个字节。
typedef unsigned int word;
3.1 寄存器的长度是一个字长
当读写double数据类型时,需要两条mov指令:
10: double dd = 15.751;
00401044 mov dword ptr [ebp-18h],126E978Dh
0040104B mov dword ptr [ebp-14h],402F8083h
当读写一个字长或以下的数据时,只需要一个寄存器,一条mov指令。
同样的,当返回值是一个字长或以下的数据时,可用寄存器返回。如果是double,则用浮点栈返回,如果是复合类型,则需要压入一个存储返回值的起始地址,将返回值返回到这个起始地址标识的被调函数的栈帧空间。
3.2 指针的标度是一个字长
printf("%d\n",sizeof(void*)); // 4,32位系统
3.3 栈按一个字长对齐
其根源还是在于寄存器的长度是一个字长,一次访问一个字长的内存空间,如果不对齐,有可能就需要更多次的访问,适当的浪费一点内存空间来换取效率(以空间换时间)是可取的。
#include <stdio.h>
void bufferOverflow()
{
char ch = 'a'; // 栈对齐为4个字节
int base = 0;
char buf[5] = {0}; // 栈对齐为8个字节
puts("输入你构造的字符串,模拟缓冲区溢出:");
gets(buf);
if(base==0x64636261){
puts("缓冲区溢出改写了邻近区内存!");
}
}
int main()
{
bufferOverflow(); // 输出12345678abcd会执行puts(),678用于栈对齐,
// abcd给到了base,'\0'给到了ch
return 0;
}
/*output:
输入你构造的字符串,模拟缓冲区溢出:
12345678abcd
缓冲区溢出改写了邻近区内存!
*/
看函数的栈帧:
栈帧图示:
如果输入超过15个字符(其中有'\0'),则会破坏ebp,引发运行错误。
结构体也需要同样的对齐(包括成员的对齐及整体的对齐):
#include <stdio.h>
struct Align{
char ch;
int base;
char buf[5];
};
int main()
{
Align align = {'a',1,"abcd"};
getchar();
return 0;
}
函数内存映像:
-End-
,免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com