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); }

2 字节级(byte)处理

典型类型: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; }

3 字级(word)处理

典型类型: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 缓冲区溢出改写了邻近区内存! */

看函数的栈帧:

c语言中的long占位符(C按位字节级)(1)

栈帧图示:

c语言中的long占位符(C按位字节级)(2)

如果输入超过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; }

函数内存映像:

c语言中的long占位符(C按位字节级)(3)

-End-

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页