C语言揭秘:02 各种数据类型

C语言揭秘:02 各种数据类型

基础类型

子曾经曰过:程序员要对自己所写程序的每个字节都了如指掌。

Talk is cheap,show me the binary code. 
                                     by 高尔基

对于数据类型的分析,采用二进制文件和运行时的内存2个方面着手。

int / unsigned int

int

int n = 10;    // 全局整型变量n,编译链接后将放到.data段,
               // 而且只有这一个变量,那它就是放到.data的起始位置

int main()  // C语言的入口函数
{
        int n1 = n;
} 
二进制文件

执行命令:

arm-none-eabi-objdump -h main.elf

得到elf文件各段的信息, 重点看.data段

main.elf:     file format elf32-littlearm

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         000000bc  00000000  00000000  00010000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000004  a0000000  000000bc  00020000  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .comment      00000031  00000000  00000000  00020004  2**0
                  CONTENTS, READONLY
  3 .ARM.attributes 0000002e  00000000  00000000  00020035  2**0
                  CONTENTS, READONLY
  • 其中Size是段的大小,.data就是4,因为int型变量n就是4个字节大小。
  • VMA是运行地址0xa0000000。
  • LMA是load memory address即加载地址,为0xbc。
  • File off为文件中的偏移,为0x20000。
  • Algn对为对齐边界位置2**2即2的2次方=4,即4字节对齐。
  • 下面的是段的相关属性,暂不表。

注意黄色高亮的地方:File off为0x00020000,表示.data段的内容放在main.elf文件的0x00020000开始的位置。

所以我们执行命令:

 hexdump -C -s 0x20000 main.elf

得到如图高亮的位置 0a,就是10进制的10。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yuDP5ghy-1622284055112)(https://lzrsvegux7.feishu.cn/space/api/box/stream/download/asynccode/?code=NGY1Njc5NjkxMzc5NDM5ZDRiZjExNDdhZWUxNjM3MjZfOFpad3hCMnFSeXZERVRWVjEwcFIxMWlWM1FhRDJKdDBfVG9rZW46Ym94Y25NcERjcGF1dTBjcDVJeTI3ZjJEcWZlXzE2MjIyODAzODc6MTYyMjI4Mzk4N19WNA)]

内存

需要用到qemu命令x和xp命令

x /fmt addr
    Virtual memory dump starting at addr.
xp /fmt addr
    Physical memory dump starting at addr.
    fmt is a format which tells the command how to format the data. Its syntax is: /{count}{format}{size}
    count 
        is the number of items to be dumped.
    format
        can be x (hex), d (signed decimal), u (unsigned decimal), o (octal), c (char) or i (asm instruction).
    size
        can be b (8 bits), h (16 bits), w (32 bits) or g (64 bits). On x86, h or w can be specified with the i format to respectively select 16 or 32 bit code instruction size.

因我们还没有启用MMU,物理地址和虚拟地址相同,我们用x或xp均可。

查看代码段的加载地址 0xbc 处的一个4字节的数据,用10进制的格式显示。执行:

x /1dw 0xbc

得到结果:10,如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1rDo9J6i-1622284055115)(https://lzrsvegux7.feishu.cn/space/api/box/stream/download/asynccode/?code=YTlkMmVjODNhOGVlYzliODQ3OTI2NTdkZTMxZWVhODFfa2E2clpHc3hFRWQ3aXZsS3J4M2xLOGpHVUhNcWtmSm9fVG9rZW46Ym94Y25CTlM4cDNhSEFjWVdGTDBhN0NhUENmXzE2MjIyODAzODc6MTYyMjI4Mzk4N19WNA)]

查看代码段的运行时地址 0xa0000000 处的一个4字节的数据,用10进制的格式显示。执行:

x /1dw 0xa0000000

得到结果:10,如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wlNY3rAX-1622284055117)(https://lzrsvegux7.feishu.cn/space/api/box/stream/download/asynccode/?code=NzU2ZDkxOGNhOGU4NzNjMDU3OTU5YzgwOTE0M2QyOThfcDV5VDd5SHlOY3RTcmVMRFBaTTFZMXdKVjRmM1d3QTRfVG9rZW46Ym94Y250WDNwMUJtZjVUV2hWOEtJYzhvTVdkXzE2MjIyODAzODc6MTYyMjI4Mzk4N19WNA)]

再试一个负数-2:

int n = -2;    // 全局整型变量n,编译链接后将放到.data段,
               // 而且只有这一个变量,那它就是放到.data的起始位置

int main()  // C语言的入口函数
{
        int n1 = n;
} 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5frdMNIG-1622284055119)(https://lzrsvegux7.feishu.cn/space/api/box/stream/download/asynccode/?code=ZmIwMTA4Y2EwNmE0MDRlZDExZDZhYmM3OTQ1ZjgxOTlfenpENzhHalVvSXZuRlNabUVEMHdmRUhJZnNCTjRpREZfVG9rZW46Ym94Y25WMENlVFducUNNVm9PQUhvdEdVVTU4XzE2MjIyODAzODc6MTYyMjI4Mzk4N19WNA)]

我们看到在内存中的16进制表示,这个是-2的补码表示。

综上:将int型变量在的分析完成。

unsigned int

对于正整数赋值给unsigned int,和int类似。重点研究一下将负整数赋值给unsigned int有什么特别的。

  • 对于无符号整形-2
unsigned int n = -2;    // 全局无符号整型变量n

int main()  // C语言的入口函数
{
        int n1 = n;
} 

查看代码段的运行时地址 0xa0000000 处的一个4字节的数据,用16进制的格式显示。执行:

x /1dw 0xa0000000

如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tRwFX1if-1622284055121)(https://lzrsvegux7.feishu.cn/space/api/box/stream/download/asynccode/?code=ZmIwMTA4Y2EwNmE0MDRlZDExZDZhYmM3OTQ1ZjgxOTlfenpENzhHalVvSXZuRlNabUVEMHdmRUhJZnNCTjRpREZfVG9rZW46Ym94Y25WMENlVFducUNNVm9PQUhvdEdVVTU4XzE2MjIyODAzODc6MTYyMjI4Mzk4N19WNA)]

说明:将整形的负数赋值给无符号整形变量,是将负数的补码赋给变量。

下面类型的分析就只dump内存的内容来分析,因为内存中的数据就是从二进制文件加载来的。有兴趣的读者可自行验证,方法与int型类似。

(unsigned )long、short、char

与int、unsigned int类似,读者可自行实验。

float

上代码:

float f = -2.5f;   

int main()  // C语言的入口函数
{
        
} 

得到:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IeYcQY6l-1622284055122)(https://lzrsvegux7.feishu.cn/space/api/box/stream/download/asynccode/?code=NTBlZmE3YTBhNjAzYmFmZmYwZWM5OTU4MjI1YzFhNjlfaDd0UTZ3VUo5aUdXYkxEU2RhSTdMMHpueDduUFVyOERfVG9rZW46Ym94Y25OYTAyTVdFdVpvUlVFM2hxUm8zRVdyXzE2MjIyODAzODc6MTYyMjI4Mzk4N19WNA)]

如图这个0xc0200000怎么来的呢?

这要从float的IEEE754标准来分析。参考:IEEE754 浮点数的表示方法_Dablelv的博客专栏-CSDN博客_浮点数表示方法

配合IEEE754标准,在网址:https://float.exposed/ 上可以直接查看输入的float数值的各种表示方式,比如本例中的-2.5,高亮的地方就是16进制的表示法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MRTOWdFd-1622284055122)(https://lzrsvegux7.feishu.cn/space/api/box/stream/download/asynccode/?code=NTE3Yzc5MzBlNGM3MGQyMzAzYTIwNjUwZDU4YmJlN2NfTGtuRkQ0dFVCeFlSZDFPcTNiUzk2bUs1UXdodmhCRzdfVG9rZW46Ym94Y25ab1VBWmRsRG1LT3lVZnBLT0txWTJjXzE2MjIyODAzODc6MTYyMjI4Mzk4N19WNA)]

float收工。

double

double d = -2.5;   

int main()  // C语言的入口函数
{
        
} 

因为double占8个字节,我们要1个16进制的g,得到:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LPgbH17d-1622284055123)(https://lzrsvegux7.feishu.cn/space/api/box/stream/download/asynccode/?code=ODhjMDI2NmY0MGE5YTk4ZTlhMGFhNTRjZDdkNjdjODRfREE2ZTF1bmxaeTk5c2dZM2Q5YUVFYUY4b3ZRTlU3VXVfVG9rZW46Ym94Y25HdlZha2htalZiOGlQVHM5ZFlhcTljXzE2MjIyODAzODc6MTYyMjI4Mzk4N19WNA)]

同float,在网址:https://float.exposed/ 输入double型的-2.5。得到:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pb6Tu8tM-1622284055124)(https://lzrsvegux7.feishu.cn/space/api/box/stream/download/asynccode/?code=MGZiNGVmYzY3YjdhYzBmZTc3OGViYzQ5ZThhY2EwZGZfVUpQdHNQMExEVE85VXpkdEtGOUY2SVkxUUZpRXR2eGdfVG9rZW46Ym94Y25QUk1xbGdOTTZmTTZpbGRKNzZNaXplXzE2MjIyODAzODc6MTYyMjI4Mzk4N19WNA)]

复合类型

指针

上代码:

int n = 0xbabebabe;
int * p ; 

int main()  // C语言的入口函数
{
        p = &n;
} 

现在我们有2个全局变量n和p,我们把.data段的开头2个32位数据用16进制形式dump出来,得到:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JyZ1oVRN-1622284055125)(https://lzrsvegux7.feishu.cn/space/api/box/stream/download/asynccode/?code=OTNiNDk3NjU0YTE4NzRkMTM3YTU0ODc4OTVkZDk2YzRfZTdVNjRveVlhREF1dXY1OHg0REFaWmxINE1aZjVhM2hfVG9rZW46Ym94Y25Kd2ZNaXMyQzkxZTlTZ0xxVU1mNnFnXzE2MjIyODAzODc6MTYyMjI4Mzk4N19WNA)]

很明显,0xa0000000开始的word值0xbabebabe就是变量n的值,也就是说变量n的地址就是0xa0000000。自然的因为p=&n;那p的值就是n的地址0xa0000000,所以第二个word的内容就是0xa0000000。符合语法中对指针的定义。

数组

struct Student
{

};
int n[4] = {1,2,3,4};

int main()  // C语言的入口函数
{
} 

Dump 4个word出来,每个word用10进制表示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eYAdYnIL-1622284055126)(https://lzrsvegux7.feishu.cn/space/api/box/stream/download/asynccode/?code=N2QwYjJkZDExZDcyNzQ1N2JlNGU2ZWU2NDUyZTE3M2ZfeDhFT1JHU3ZVdTI2QXk0QUV2WkROektFaE42endEeWFfVG9rZW46Ym94Y25ZZ2NrQ3gyR3BMM0ZsMHpSeWNURGxjXzE2MjIyODAzODc6MTYyMjI4Mzk4N19WNA)]

看起来没啥特别的。

结构体

struct Student
{
        int id;
        int age;
        int class_id;
        char name[32];
};

char* my_strcpy(char* dst, const char* src, int size)
{
        int i;
        for ( i = 0; i < size; i++)
        {
                dst[i] = src[i];
        }
        
        return dst;
}

struct Student std;
int main()  // C语言的入口函数
{
        std.id = 123;
        std.age = 12;
        std.class_id = 2;
        my_strcpy(std.name, "zhang san", 10);

        return 0;
} 

代码定义了一个全局结构体变量std。又因只有这个变量,那它就放到了数据段的起始地址处。所以我们还是dump 0xa0000000的数据:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IDlM3m5D-1622284055127)(https://lzrsvegux7.feishu.cn/space/api/box/stream/download/asynccode/?code=MDc2OWRlNjkwMzRlMGJiODNmZDJlMGM3Yzc2YTE3YjVfejlyN0p4MVIyMVNjZjlrMEhmVDBGV1Jnd25pVjhLTUdfVG9rZW46Ym94Y25Tb3p1NWNZWHp0c2hLbEt0MlRsM1BkXzE2MjIyODAzODc6MTYyMjI4Mzk4N19WNA)]

可以看到,我分成了2步来dump,显示dump 4个10进制表示的word,前面的3个word就分别表示了这个变量的id, age, class_id,和代码中是匹配的,但是第4个word的值看不出来是啥,那是因为第4个word开始它是字段name的内容,它的大小是32个字节,我们就dump 32个char型的数据,可以看到前面的10个字符就是我们赋值的zhang san,没啥毛病。

联合体

对于联合体,我们设想一个需求场景,有一天学校需要限制学生的头发和胡须的长度,那同学胡须不能超出2毫米,女同学头发不能超出500毫米。

那我们就可以这样设计数据结构:用一个联合体表示2中长度:

  • 如果是男同学,就表示胡子长度,单位毫米,为了精确点,用float类型;
  • 如果是女同学,就表示头发长度,单位毫米,没必要精确到毫米以下,我们就用int型。

如下就是我们的代码,我们用2个变量分别表示男同学和女同学的要求,当然都是union body_hair_len类型:

union body_hair_len       // 学生头发或胡须的长度
{
        float beard_len;  // 如果是男同学,就表示胡子长度,单位毫米,为了精确点,用float类型
        int hair_len;     // 如果是女同学,就表示头发长度,单位毫米,没必要精确到毫米以下,我们就用int型
};


union body_hair_len male_len1;
union body_hair_len female_len2;
int main()  // C语言的入口函数
{
        male_len1.beard_len = 2.0f;
        female_len2.hair_len = 500;

        return 0;
} 

我们dump出来4个word吧,第一word是female_len2中存的值500, 第二个word存的是male_len1中 beard_len的值,因它是个浮点数,见上面float的表示方法,可以得到图中的值。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ko6boSij-1622284055128)(https://lzrsvegux7.feishu.cn/space/api/box/stream/download/asynccode/?code=MzZhOWIyMTI3MjlkMzM4ODMxYTQ2NTFiMmI1ZGJmOThfaFpzeHhRcXBWODRpdWJPUVJvSFJVZVVEUHVoQnc0bEhfVG9rZW46Ym94Y25VR2ZUN3NNR0x0QzQ5QUhyWUZCNTRnXzE2MjIyODAzODc6MTYyMjI4Mzk4N19WNA)]

你是不是有个疑问:为啥不是第一个word保存的male_len1,第二个word保存的female_len2?

() // C语言的入口函数
{
male_len1.beard_len = 2.0f;
female_len2.hair_len = 500;

    return 0;

}


我们dump出来4个word吧,第一word是female_len2中存的值500, 第二个word存的是male_len1中        beard_len的值,因它是个浮点数,见上面float的表示方法,可以得到图中的值。

[外链图片转存中...(img-ko6boSij-1622284055128)]

你是不是有个疑问:为啥不是第一个word保存的male_len1,第二个word保存的female_len2?

那是因为该编译器在将这2个变量解析的时候,会按照变量名的字母顺序排序,字母在前的变量就放到前面。f在m前,所以就是这个结果。当然不见得所以的编译器都会按照这个排序的规则来放数据。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值