欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C语言14-结构体内存布局及对齐、共用体

程序员文章站 2022-05-12 08:48:02
...

内存断点

现在想确认“什么时候g_Brick中的数据会被修改”。
可以使用内存断点,更快地解决这个问题。
所谓内存断点(数据断点)其实就是可以对内存中某块数据进行监视,如果程序有对该数据进行访问的行为,那么触发该断点。
如何设置
在“调试状态下”,在调试菜单中“新建断点”->“新建数据断点”。

VS中生成静态版本的exe

到目前为止,VS默认的生成exe都是使用的动态库。它的好处是体积小。
还可以生成静态库版本的exe,这样通用性更好。

结构体的内存布局

结构体是非基本数据类型,那么他的内存布局是怎样的呢?
先从结构体的大小来观察下:

struct tagStruct1
{
    int nValue1;
    int nValue2;
    int nValue3;
    int nValue4 ;
};

int main(int argc, char* argv[])
{
    printf("%d\r\n", sizeof(tagStruct1));
    tagStruct1 Str1;
    Str1.nValue1 = 0x11111111;
    Str1.nValue2 = 0x22222222;
    Str1.nValue3 = 0x33333333;
    Str1.nValue4 = 0x44444444;
    return 0;
}

(暂时)可以看出,结构体的大小,就是所有成员大小之和。
那么,剩下的问题就是只有:成员在内存中的放置顺序是如何的?
经验证:

得出结论:

  • 结构体的大小,就是所有成员的总和
  • 先定义的成员,出现咋结构体较低的地址处

结构体指针到底是如何寻址的

struct tagStruct1
{
    int nValue1;
    int nValue2;
    int nValue3;
    int nValue4;
};

void Find(tagStruct1* ptr)
{
    printf("%p\r\n", &ptr->nValue1);
    printf("%d\r\n", ptr->nValue2);
}

tagStruct1 g_struc;

int main(int argc, char* argv[])
{

    tagStruct1* obj = &g_struc;
    obj->nValue2 = 3;
    Find(obj);
    return 0;
}

对于以上的函数,参数只有一个指针(结构体的首地址)。凭什么可以通过这个首地址,找到各个成员的值呢?

  • 根据首地址和头文件中结构体的定义,找到成员的首地址
  • 根据头文件中记录的成员的解释方式,将上一步的地址中的值取出

结构体内存对齐

以上,我们已经了解了结构体内存布局的基本核心。
但是,这不足以解释结构体布局的所有方面,比如:

struct tagStruct1
{
    double dbValue1;
    char chValue2;
    int nValue;
};

struct tagStruct2
{
    char chValue2;
    double dbValue1;
    int nValue3
};

int main (int argc, char* argv[])
{
    printf("%d\r\n", sizeof(tagStruct1));
    printf("%d\r\n", sizeof(tagStruct2));
    return 0;
}

两个结构体大小的输出,是不一样的。这就是:结构体对齐问题

结构体对齐问题的起源

早期的计算机中,为了效率,会对数据在内存中存在的首地址,有一定要求。
比如,int存在的首地址,必须要在4的倍数上。
double存在的首地址,必须在8的倍数上。
以上这样做,是为了存储和读取效率。
这种习惯,依然沿用了C语言编译器的默认选项中。
对于结构体的对齐(VS默认配置),要求如下:

  • 结构体第一个成员的偏移为0(永远是对齐的)
  • 之后的成员,其偏移都必须是自己sizeof(type)的整数倍
  • 结构体本身也是有对齐的要求:我们想象,当前结构体之后,会紧接一个同类型的结构体。那个同类型的结构体的偏移的对齐值是:结构以中所有成员对齐值的最大值。
  • 实际上,还要考虑工程上对齐值的设置。所有对齐值的选择中,应该是“对齐规则中的对齐值”和“工程设置的对齐值”,两者取较小者。
struct tagTest2
{
    char chValue2;//offset 0, size: 1
    double dbValue1;//offset 8, size : 8
    int nValue3;//offset 16, size: 4
};
//因为结构体本身的对齐值是sizeof(double) = 8.所以最后的结构体大小,不是20,而是24.

共用体

共用体通结构体类似,都是非基本数据类型,所以需要先声明公用体新类型,在用新类型去定义新变量。
公用体的关键字是union。

union UnionBlackBoard
{
    double dbSalary;
    char szName[8];
    int nNoticeID;
};

int main(int argc, char* argv[])
{
    UnionBlackBoard unionObj;
    unionObj.dbSalary = 1000; 
    printf("%lf\r\n", unionObj.dbSalary);

    unionObj.nNoticeID = 17; 
    printf("%d\r\n", unionObj.nNoticeID);

    strcpy(unionObj.szName, "Zhang"); 
    printf("%s\r\n", unionObj.szName); 
    return 0; 
}

union的使用和结构体有类似之处。
同样也有不同之处,更加重要的是主要体现在内存布局上:

  • struct中, 各个成员的内存区域嘞,是不重叠的
  • union中,所有成员,公用同一块内存空间,所有成员的首地址重合。

union的应用

如何快速地转换float类型和其对应的16进制

union TransferFloat
{
    float fltValue;
    unsigned char chAry[4];
}

int main (int argc, char* argv[])
{
    TransferFloat myunion;
    myunion.fltValue=3.14;
    for (szie_t i=0;i<4;i++)
    {
        printf("%02X", myunion.chAry[i]);
    }
    return 0;
}

union在IP转换上的应用
什么是IP?
C语言14-结构体内存布局及对齐、共用体
IPV4的IP,其实就是一个int数字而已。只不过为了人方便读取,才发明了点的记录方法(172.254.112.3)(注意,每三位数的最大值不操过255

union IPV4IP
{
    int nValue;
    unsigned char chAry[4];
};