龙空技术网

C|按位、字节级、字长级数据处理

小智雅汇 689

前言:

此时各位老铁们对“c语言汉字几个字节”大概比较重视,姐妹们都想要学习一些“c语言汉字几个字节”的相关文章。那么小编也在网络上汇集了一些对于“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  //0x0000011110  //0x1e00110000  //0x3000110000  //0x3000011100  //0x1c00110000  //0x3000110000  //0x3000011110  //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%10;        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;}/*1102631-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 bitsMonth(1-12)4 bitsDay(l-31)  5 bits如2021/11/22         1234567890123456         00000000000000000000000000010101           // 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%16;        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],126E978Dh0040104B   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-

标签: #c语言汉字几个字节