【C语言】结构体 深入 2(传参,位段)

发布于:2025-04-21 ⋅ 阅读:(44) ⋅ 点赞:(0)

前言:

在上一章节讲解了结构体内存对齐,知道了如何计算字节的大小,储存的方式。

在本章节继续为大家讲解结构体的运用,(传参,位段)

一·结构体传参

结构体传参是函数调用中的一种常见操作,其主要目的是传递复杂数据类型的信息。根据不同的需求,结构体传参可以分为两种方式:传值传址

1. 传值调用

传值调用是指将结构体的副本传递给函数。这种方式虽然简单,但缺点是效率较低,因为每次传递都需要复制整个结构体,这会占用较大的内存空间并增加时间开销。

例如:

#include <stdio.h>
struct S
{
    char aee;
    int shu;
    int num;
    int id;
};

void printStruct(struct S s) 
{

    printf("num = %d %d\n", s.num,s.id);
}

int main() 
{
    struct S s = { 1, 2, 3, 4 };
    printStruct(s); // 传值调用
    return 0;
}

在上述代码中,printStruct函数接收一个struct S类型的参数s,由于是传值调用,因此函数内部对s的修改不会影响到外部变量。

2. 传址调用

传址调用是指将结构体的地址传递给函数。这种方式效率更高,因为它只需要传递结构体的指针,而不需要复制整个结构体。例如:

#include <stdio.h>
struct S
{
    char aee;
    int shu;
    int num;
    int id;
};

void printStruct(struct S* ps)
{
    printf("num = %d\n", ps->num);
}

int main()
{
    struct S s = { 1, 2, 3, 4 };
    printStruct(&s); // 传址调用
    return 0;
}

 

在上述代码中,printStruct函数接收一个指向struct S的指针ps,通过解引用ps可以访问到原始结构体的内容。这种方式避免了不必要的内存开销,提高了程序性能。

总结
  • 推荐使用传址调用:传值调用虽然简单,但在结构体较大时会导致较大的内存开销和时间消耗。因此,在实际开发中,建议优先选择传址调用

二· 结构体的位段

什么是位段?

位段是一种特殊的结构体成员类型,其基本单位是“位”(bit),即二进制中的0或1。位段通过在单个整数变量中分配特定数量的位来存储数据,从而节省内存空间。位段的成员类型通常为intunsigned intsigned intchar,并且成员名后需要加上冒号和数字来指定占用的位数。

位段的声明方式如下:

struct 结构体名 
{
    数据类型:位宽 成员名;
    ...
};

例如:

struct flags {
    unsigned int flag1:1; // 占用1位
    unsigned int flag2:2; // 占用2位
    unsigned int flag3:3; // 占用3位
    int _a : 2;
};

这种结构允许开发者在内存中高效地存储少量数据,例如标志位、配置参数等。 

在这个例子中,flag1占用1位,flag2占用2位,flag3占用3位。整个结构体占用的字节数取决于这些位段的总宽度是否超过一个字节。(这里重新定义的大小为bit,1字节(Byte)等于8比特(bit) 

注意:

  1. 位段的成员必须是(整型):int,unsigned int 或signed int,char
  2. 位段的成员名后边有一个冒号和一个数字
  3. 位段成员名中的 "_" 是可以是可以省略的,加上下划线与不加都可以,只是一种命名风格。(如:int _a 可以修改为int a)
  4. 位段中分配内存的大小,宽度必须小于等于指定类型的位宽度。(即:冒号后面的数字的bit不能超过前面类型所占的bit)
  5. 位段的位指的是二进制位。

位段的内存分配

位段的内存分配方式与普通结构体成员不同。位段的存储空间按字节对齐,但分配方式取决于成员类型和编译器实现。通常情况下,位段的分配规则如下:

  • 按字节对齐:位段的存储空间通常以4字节(int)或1字节(char)为单位开辟。
  • 成员顺序:位段成员在内存中的分配顺序可能因编译器和平台而异。有的编译器从左到右分配,有的从右到左分配,这可能导致跨平台兼容性问题。
  • 大小端影响:大小端模式可能影响位段成员的存储顺序。例如,在小端模式下,低位字节存储在低地址,而在大端模式下,高位字节存储在低地址。 

需要注意的是,位段不能跨字节边界存储,即所有位段成员必须在同一字节内完成分配

大家可以预计一下输出结果

#include <stdio.h>
struct S
{
	  char a : 3;
	  char b : 4;
	  char c : 5;
	  char d : 4;
};
	
	
int main()
{
    struct S s = { 0 };
    printf("%zd\n", sizeof(struct S));
	s.a = 10;
	s.b = 12;
    s.c = 3;
    s.d = 4;
	
	return 0;
}

 结果是4嘛?

答案:  不是。

是3,这里 

原因如下:

PS:这是申请的一个字节是从走向右使用,还是从右向左使用,是不确定的。

这里假设是 是从右向左使用。

在一个字节中8个bit,当一个结构没占满8个时,剩余的大小如果够下一个结构使用,则共同占一个字节大小,如果,剩余的bit大小 小于 结构大小,则空间浪费。

开创下一个字节来存储。

还有赋值的大小为二进制,如:  a的二进制时1010,但这里仅仅给a3个bit,所以储存进去的为010.

跨平台问题

位段由于其特性,在跨平台使用时存在多个不确定因素,主要体现在以下几个方面:

  • 符号性不确定:在某些平台上,int类型的位段可能被视为有符号数,而在其他平台上可能被视为无符号数。
  • 最大位数不确定性:不同平台对int类型的最大位数支持不同。例如,在16位系统中,int最多支持16位,而在32位系统中,int最多支持32位。如果在32位系统中定义了超过32位的位段,则可能导致错误。
  • 成员分配顺序不明确:不同编译器对位段成员的分配顺序没有统一标准,可能导致跨平台兼容性问题。
  • 剩余空间处理:当两个位段成员之间剩余空间不足时,编译器可能会舍弃剩余空间或直接放置第二个成员,但具体行为未定义。

因此,注重可移植性的程序应尽量避免使用位段

应用场景

位段的主要应用场景包括:

  • 节省内存:在需要存储少量数据(如标志位)时,使用位段可以显著减少内存占用。例如,在网络协议中,通过位段存储状态标志可以减少数据包大小。
  • 高效配置管理:在嵌入式系统中,通过位段存储设备配置参数可以提高配置管理的灵活性和效率。
  • 消息传输优化:在消息传输协议中,利用位段压缩数据字段,可以减少传输开销。

注意事项

使用位段时需要注意以下几点:

  • 避免跨平台问题:尽量在单平台环境下开发和测试程序,避免依赖特定平台的特性。
  • 明确成员类型和大小:确保所有位段成员类型一致,并且总位数不超过类型的最大支持范围。
  • 注意符号性问题:在使用有符号数类型的位段时,需明确其符号性,以避免运行时错误。
  • 合理设计结构体:避免在一个结构体中同时使用多种类型的位段成员,以免增加复杂性

总结

位段作为经典的内存优化技术,在嵌入式开发、协议解析等场景中仍有重要价值。但在现代软件开发中,需要权衡其内存优势与潜在的可移植性风险。对于新项目,建议优先考虑可移植性更好的位掩码方案;而在资源严格受限的嵌入式场景,合理使用位段仍是不二之选。记住:任何优化都要以可维护性为前提!

好了,本章到此结束了!!

感兴趣的,关注一手博主,我们下期再见!!