news 2026/9/3 22:59:16

【c语言】1.4 彻底理清:字符串/结构体/枚举

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【c语言】1.4 彻底理清:字符串/结构体/枚举
  • 1.C语言中字符串的本质:

指针指向头、固定尾部的地址相连的一段内存。C语言中字符串有3个核心要点:第一是用一个指针指向字符串头;第二是固定尾部(字符串总是以'\0'来结尾);第三是组成字符串的各字符彼此地址相连。

  • 2.char *p = "linux";

在这段代码中,p本质上是一个字符指针,占4字节;"linux"分配在只读常量区,占6个字节;实际上总共耗费了10个字节。sizeof测多少字节。strlen测字符串长度(不算'\0'),注意:只能传递合法的字符串才有意义

  • 3.字符数组与字符串的本质差异(内存分配角度)

(1)字符数组char a[] = "linux";来说,定义了一个数组a,数组a占6字节,右值"linux"本身只存在于编译器中,编译器将它用来初始化字符数组a后丢弃掉(也就是说内存中是没有"linux"这个字符串的);这句就相当于是:char a[] = {'l', 'i', 'n', 'u', 'x', '\0'};

(2)字符串char *p = "linux";定义了一个字符指针p,p占4字节;同时还定义了一个字符串"linux",分配只读常量区,然后把常量区中的字符串(一共占6字节)的首地址(也就是'l'的地址)赋值给p。

总结对比:字符数组和字符串有本质差别。字符数组本身是数组,数组自身自带内存空间,可以用来存东西(所以数组类似于容器);而字符串本身是指针,本身永远只占4字节,而且这4个字节还不能用来存有效数据,所以只能把有效数据存到别的地方,然后把地址存在p中。也就是说字符数组自己存那些字符;字符串一定需要额外的内存来存那些字符,字符串本身只存真正的那些字符所在的内存空间的首地址。

  • 4.结构体为何要对齐访问

结构体中元素对齐访问主要原因是为了配合硬件,也就是说硬件本身有物理上的限制,如果对齐排布和访问会提高效率

对结构体取地址,是一字节类型的地址。错误理解!! &地址+1:加的是一个地址类型

  • 5.内存对齐

关于结构体内存:(假设是4字节对齐)

1.想象一个以4字节为宽度的的矩形

2.一个 填充 的概念

3.注意每一个元素都有自己的对齐规则(判断填充,所以要看后面的类型,后面是前面的整数倍),如short(2字节),只能放在类似0,2,4这样的偶地址处

4.整个结构体是最宽元素的整数倍

例子:

struct mystruct //一共8字节

{

int a; //4

char b; //1+1

short c; //2

}

内存分配如图

  • gcc支持但不推荐的对齐指令:#pragma pack() #pragma pack(n) (n=1/2/4/8)

#pragma是用来指挥编译器,常用对齐命令:1. #pragma pack(),这种就是设置编译器1字节对齐 2. #pragma pack(4),这个括号中的数字就表示我们希望多少字节对齐。

  • gcc推荐的对齐指令__attribute__((packed)) __attribute__((aligned(n)))

__attribute__((packed))使用时直接放在要进行内存对齐的类型定义的后面,然后它起作用的范围只有加了这个东西的这一个类型。packed的作用就是取消对齐访问。

__attribute__((aligned(n)))使用时直接放在要进行内存对齐的类型定义的后面,然后它起作用的范围只有加了这个东西的这一个类型。它的作用是让整个结构体变量整体进行n字节对齐(注意是结构体变量整体n字节对齐,而不是结构体内各元素也要n字节对齐)

例子:

struct mystruct

{

int a;

char b;

short c;

} __attribute__((packed)); //取消对齐访问
// 那就是 4+1+2 =7字节

typedef struct mystruct111

{

int a;

char b;

short c;

short d;

}__attribute__((aligned(1024))) My111;
// 4 + 1 + 1(填充) + 2 + 2 =10字节,但是要填充1014字节

  • offsetof宏的原理

#define offsetof(TYPE,MEMBER) ( (int) &((TPYE *)0)->MEMBER ) //返回的是member元素相对整个结构体变量的首地址的偏移量,int类型

例:int offsetof_a= offsetof(struct stu, a)

(TYPE *)0 这是一个强制类型转换,把0地址强制类型转换成一个指针,这个指针指向一个TYPE类型的结构体变量。 (实际上这个结构体变量可能不存在,但是只要我不去解引用这个指针就不会出错)。

((TYPE *)0)->MEMBER (TYPE *)0是一个TYPE类型结构体变量的指针,通过指针指针来访问这个结构体变量的member元素

&((TYPE *)0)->MEMBER 等效于&( ((TYPE *)0)->MEMBER ),意义就是得到member元素的地址。

  • container_of宏

#define container_of(ptr, type, member ) ( { \

const tpyeof(((type *) 0) ->member) * __mptr = (prt); \

( type * ) ((char * ) __mptr - offsetof(type , member )) ; } ) //ptr 是指向结构体元素member的指针,作用:知道一个结构体中某个元素的指针,反推这个结构体变量的指针, 宏返回的是整个结构体变量的指针(由此可以得到其他结构体的指针)

例子:

char *ptr = &str.ch;

struct struct *p = container_of(ptr, struct srt, ch);

typeof(a) 由变量a得到a的类型;

const typeof( ((type *) 0) ->member ) * __mptr = (prt); //得到元素类型的指针:例const char* __mptr = prt ;

( type * ) ((char * ) __mptr - offsetof(type , member )) ; //先用typeof得到member元素的类型定义成一个指针,然后用这个指针减去该元素相对于整个结构体变量的偏移量(偏移量用offsetof宏得到的)(注意转成char *),减去之后得到的就是整个结构体变量的首地址了,再把这个地址强制类型转换为type *即可! 没问题

  • 共用体union

共用体union就是对同一块内存中存储的二进制的不同的理解方式。

union的sizeof测到的大小是union中各个元素里面占用内存最大的那个元素所占字节的倍数

  • 大端模式(big endian)和小端模式(little endian)

小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中。ARM是小端模式。

2种测试方法:

//通过指针

int test_endian1(void)

{

int i = 1;

char c = *((char *)(&i));

return (1 == c);

}

//通过union

union myUnion

{

int i;

char c;

};

int test_endian2(void)

{

union myUnion u1;

u1.i = 1;

return (u1.c == 1);

}

思考几种看似可行,其实不行的方式: 原因都是这个运算是高于内存层次的

// 位与

int a = 1;

int b = a & 0xff;

printf("b = %d.\n", b);

理论分析:位与运算是编译器提供的运算,这个运算是高于内存层次的(或者说&运算在二进制层次具有可移植性,也就是说&的时候一定是高字节&高字节,低字节&低字节,和二进制存储无关)。

// 移位

int a, b;

a = 1;

b = a >> 1;

printf("b = %d.\n", b);

理论分析:原因和&运算符不能测试一样,因为C语言对运算符的级别是高于二进制层次的。右移运算永远是将低字节移除,而和二进制存储时这个低字节在高位还是低位无关的。

// 强制类型转换

int a;

char b;

a = 1;

b = (char)a;

printf("b = %d.\n", b);

分析:同上

  • 枚举

默认从0开始,初始直接赋值。

宏定义与枚举区别:

(1)枚举是将多个有关联的符号封装在一个枚举中,而宏定义是完全散的。也就是说枚举其实是多选一。

(2)什么情况下用枚举?当我们要定义的常量是一个有限集合时(譬如一星期有7天,譬如一个月有31天,譬如一年有12个月····),最适合用枚举。(其实宏定义也行,但是枚举更好)

(3)不能用枚举的情况下(定义的常量符号之间无关联,或者无限的)用宏定义。

总结:宏定义先出现,用来解决符号常量的问题;后来人们发现有时候定义的符号常量彼此之间有关联(多选一的关系),用宏定义来做虽然可以但是不贴切,于是乎发明了枚举来解决这种情况。

enumeration 类型定义

// 定义方法1,定义类型和定义变量分离开 enum week { SUN, // SUN = 0 MON, // MON = 1; TUE, WEN, THU, FRI, SAT, }; enum week today; */

// 定义方法2,定义类型的同时定义变量 enum week { SUN, // SUN = 0 MON, // MON = 1; TUE, WEN, THU, FRI, SAT, }today,yesterday; */

// 定义方法3,用typedef定义枚举类型别名,并在后面使用别名进行变量定义 typedef enum week { SUN, // SUN = 0 MON, // MON = 1; TUE, WEN, THU, FRI, SAT, }week; */

错误类型举例

// 错误1,枚举成员重名,编译时报错:redeclaration of enumerator ‘MON’ typedef enum workday { MON, TUE, WEN, THU, FRI, }workday; typedef enum weekend { MON, SAT, SUN, }weekend;

// 结构体中元素可以重名 typedef struct { int a; char b; }st1; typedef struct { int a; char b; }st2;

解释: 不能有重名的枚举成员。 分析:经过测试,两个struct类型内的成员名称可以重名,而两个enum类型中的成员不可以重名。实际上从两者的成员 在访问方式上的不同就可以看出了。struct类型成员的访问方式是:变量名.成员,而enum成员的访问方式为:成员名。 因此若两个enum类型中有重名的成员,那代码中访问这个成员时到底指的是哪个enum中的成员呢? 两个#define宏定义是可以重名的,该宏名真正的值取决于最后一次定义的值。编译器会给出警告但不会error

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/3 16:03:13

快速把 Mermaid 流程图的布局切到 ELK:一份完整配置指南

快速把 Mermaid 流程图的布局切到 ELK:一份完整配置指南 【免费下载链接】mermaid Generation of diagrams like flowcharts or sequence diagrams from text in a similar manner as markdown 项目地址: https://gitcode.com/GitHub_Trending/me/mermaid 在…

作者头像 李华
网站建设 2026/9/3 19:02:49

步骤级防护栏:大模型Agent安全防护实战解析

大模型应用跑到 Agent、RAG、多步工具调用阶段之后,“安全防护栏(Guardrails)”这件事的复杂度突然上了一个台阶。传统做法是在模型给出完整回复之后,再做一次关键词匹配或分类打分,命中了就拦截。这个思路对短对话还够…

作者头像 李华
网站建设 2026/9/4 6:14:41

自托管推理服务与Agent框架集成:从部署到批量任务实战指南

这次我们来看一个很实际的方向:把 Agent 跑在自托管推理服务上。也就是不直接调用云端大模型 API,而是在自己的服务器或本地电脑上部署一套大模型推理服务,再让 Agent 框架通过接口去调用它。对于关心数据隐私、调用成本、上下文复用和批量任…

作者头像 李华
网站建设 2026/9/3 21:24:58

从ESXi主机RAID在线扩容到Linux虚拟机LVM磁盘扩展

1. 从物理层到虚拟化的完整扩容链路 当ESXi主机的本地存储空间告急时,传统做法往往需要停机维护,这对业务连续性要求高的场景简直是噩梦。现在我们可以像给手机换存储卡一样,实现从物理磁盘到虚拟机文件系统的 全链路在线扩容 。整个过程就…

作者头像 李华
网站建设 2026/8/31 18:55:33

LocalSend AppImage 打包指南:Linux 跨发行版兼容实战与避坑清单

LocalSend AppImage 打包指南:Linux 跨发行版兼容实战与避坑清单 【免费下载链接】localsend An open-source cross-platform alternative to AirDrop 项目地址: https://gitcode.com/GitHub_Trending/lo/localsend LocalSend 是一款开源的局域网文件传输工具…

作者头像 李华