C语言自定义数据类型详解(二)——结构体类型(下)

书接上回,前面我们已经给大家介绍了如何去声明和创建一个结构体,如何初始化结构体变量等这些关于结构体的基础知识。下面我们将继续给大家介绍和结构体有关的知识:

今天的主题是:结构体大小的计算并简单了解一下位段的相关知识。

目录

一、结构体的内存对齐

(1)发现并提出问题: 

(2)内存对齐规则:

(3)解决问题:

附一:offsetof宏的介绍:

(4)为什么会存在结构体的内存对齐:

附二:带来的启发:

附三:修改编译器的默认对齐数:

附四:关于前面问题的解答:

二、关于位段:

(1)搭建应用场景:

(2)位段的概念性理解:

(3)位段的内存分配:


学习指南:如果你是期末备考的大学生,简单学习博主的结构体类型(上)的内容就够了。如果你对自己有更高的要求,希望系统深入了解结构体知识可以继续跟上博主的步伐啦!

一、结构体的内存对齐

(1)发现并提出问题: 

如果要谈结构体大小的计算,就不得不提结构体的底层特性——内存对齐。什么是内存对齐呢?我们先来看一下,下面这两个例子:

#include<stdio.h>
struct S1
{
	char c1;
	char c2;
	int i;
};

int main()
{
    printf("%zd\n", sizeof(struct S1));
    return 0;
}

 这个结构体大小是多少呢?是不是 1 + 1 + 4(sizeof(char) + sizeof(char) + sizeof(int))的结果呢?,我们不妨在自己的机器上运行一下。

下面这张截图是在博主2022 Visual Studio这款IDE上运行的结果:

这个结果挺莫名奇妙的,我一时也想不清楚这个结果为啥是这个。嗯……我们现在不妨再来做一个事情,我们继续声明和创建一个S2的结构体,然后呢,这个结构体有和S1一样的成员变量(c1,c2,i)。然后只是改一下它们之间的顺序,我们来看一下这个结构体变量的大小又是多少。如图所示:

#include<stdio.h>
struct S1
{
	char c1;
	char c2;
	int i;
};
struct S2
{
	char c1;
	int i;
	char c2;
};

int main()
{
	printf("%zd\n", sizeof(struct S1));
	printf("%zd\n", sizeof(struct S2));
	return 0;
}

运行截图:

 这也太不可思议了,我仅仅是改了一下结构体各个成员变量的顺序,竟然就改变了整个结构体变量的大小。显然种种迹象均表明:结构体变量的大小并不是各个成员变量大小的简单相加。

那这具体是怎回事呢,欸,我们接着往下探索!

(2)内存对齐规则:

前面我们通过实验发现,结构体变量的大小不是其各个成员变量的简单相加。其实其本质的原因就在于有内存对齐的底层特性存在。

那关于结构体的内存对齐有哪些规则呢?这里博主总结了关于内存对齐这一块,大致有以下四条规则:

  1. 第一个成员在与结构体变量偏移量为0的地址处;
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处;
  3. 结构体的总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍;
  4. 如果嵌套了结构体,嵌套结构体要对齐到自己的最大对齐数的整数倍处,其他规则照旧。

上述规则中的一些概念,我们这里做简单的阐释。首先关于什么是对齐数

对齐数 = Min(编译器默认的一个对齐数,成员变量自身的大小)

一般来说:结构体的每一个成员变量都有自己的一个对齐数,不同大小的成员变量一般有不同的对齐数,其中我们把一个结构体它所有成员变量中最大的那个对齐数,称之为最大对齐数。

特别地,如果成员变量是一个数组。那这里成员变量的大小,注意啦,不是指数组的大小,而是数组中单个元素的大小。我们下面以VS 2022为例子(编译器的默认对齐数是8),来说明这个问题:

struct S
{
    char c;       //8 < 1 —— 对齐数是1
    int  i;       //8 < 4 —— 对齐数是4
    char name[12];//8 < 12 ?No,No,No!    8 < 1 ——对齐数是1
};

另外关于编译器的默认对齐数,如果通过查阅相关编译器的技术文档,你能够查到该编译器的默认对齐数那当然再好不过了。但是如果说你实在查不到,那你就忽略这个编译器的默认对齐数(或者说假设它是无穷大)。如果说此时发现假设和实际结果不相一致,我们通过对结果进行分析,也可以推导出编译器的默认对齐数的大小。

那话说回来,我们还是希望大家记一些常见的关于编译器的默认对齐数:像VS系列都是8,然后gcc系列是无穷大,无穷大的另一种理解就是:对齐数 = 成员变量自身的大小。

(3)解决问题:

OK,当然啦,说了这么多,我们就可以分析和解决前面所提到的那个问题。我们这里假设是在VS 2022的环境下,也就是说编译器的默认对齐数是8。

#include<stdio.h>
struct S1
{
	char c1;
	char c2;
	int i;
};
struct S2
{
	char c1;
	int i;
	char c2;
};

int main()
{
	printf("%zd\n", sizeof(struct S1));
	printf("%zd\n", sizeof(struct S2));
	return 0;
}

对于结构体struct S1而言,它的内存布局即为下图所示:

分析:

红色块是c1的内存空间,旁边的数字对应的是这个内存块相较于结构体起始地址的偏移量。

由于c1是第一个成员,因此偏移量为0。而且c1只需要1个字节的空间,综上红色块就是c1的内存空间。

绿色块是c2的内存空间,这是因为: 8(编译器默认对齐数)< sizeof(c2) == 1,因此对于c2来说它的对齐数就是1,也就是说c2内存的起始位置在偏移量为1的倍数的位置上,显然就要我们绿色块的位置上。

蓝色块是i的内存空间,这是因为:8(编译器默认对齐数)< sizeof(i) == 4,因此对于i来说它的对齐数就是4,偏移量为4的倍数的位置,恰好就是对应我们蓝色块的起始位置。然后往下延展4个字节,这是因为sizeof(i) == 4。

最后,显然最大对齐数就是4,此时c1 + c2 + i的内存大小是8,正好就是4的倍数,因此结构体的内存大小是8。

 

对于结构体struct S2而言,它的内存布局即为下图所示:

分析:

c1,i,c2的分析略,这里我们简单说一下结构体的大小分析。对于这个结构体我们不难推导出最大对齐数——4,然后此时c1 + i + c2的内存大小是9,9不是最大对齐数4的倍数,于是向下延展至12个字节,此时正好是4的倍数,由此结构体变量的大小是12。

如果你现在觉得自己强得可怕,不妨试着继续挑战一下,下面这两个结构体大小的计算:

struct S3
{
    char c1;
    int i;
    char name[12];
};
struct S4
{
    int i;
    struct S3 s;
    char c1;
};

对于结构体struct S3而言,大家只需要注意博主给大家提醒的如果成员变量是一个数组。那对齐数那里所说的成员变量的大小,不是指数组的大小,而是数组中单个元素的大小。因此最后结构体的最大对齐数也不是12,而是4。结构体整体的大小就是20。如图所示:

其次对于结构体struct S4而言,它里面嵌套了其他的结构体struct S3 s,我们只要注意s的对齐数是这个结构体的最大对齐数。就可以了。如果所有的一些没有问题,最后struct S4的大小就是28。如图所示:

附一:offsetof宏的介绍:

offsetof是一个宏,有了offsetof我们的用户,只需要给出一个结构体,然后再给出这个结构体的一个成员变量,你就可以获得:这个成员变量距离结构体起始位置的偏移量。然后如果你想使用这个宏,请#include<stddef.h>

我们这里以前面的struct S4结构体为例子,来看一下这个宏具体是如何使用的:

#include<stdio.h>
#include<stddef.h>
struct S3
{
    char c1;
    int i;
    char name[12];
};
struct S4
{
    int i;
    struct S3 s;
    char c1;
};


int main()
{
    printf("struct S4 的i 的相较于起始位置的偏移量:%zd\n", offsetof(struct S4, i));
    printf("struct S4 的s 的相较于起始位置的偏移量:%zd\n", offsetof(struct S4, s));
    printf("struct S4 的c1的相较于起始位置的偏移量:%zd\n", offsetof(struct S4, c1));
	return 0;
}

运行截图:

而这些和我们所分析的结果是一致的。

有小伙伴可能对这个宏的底层实现比较感兴趣,我们这里也把这个offsetof宏的一种实现方式给大家放在下面了:

#define offsetof(type, member) ((size_t)&((type*)0)->member)

它的基本策略就是把地址为0的位置当作是结构体的起始地址,然后结构体的成员member再取地址就是这个结构体成员相较于起始位置的偏移量了。

(4)为什么会存在结构体的内存对齐:

C语言的结构体,为什么会有内存对齐的特性。有一种说法是说如果结构体的内存是对齐的话,会带来更好的内存访问效率。

OK,为了更好的说明这个问题,我们先来假定一些场景:

假如我们的用户创建了结构体S,并定义了一个该结构体的变量s,如图所示:

struct S
{
    char c;
    int i;
}s;

然后我们假定我们拥有一台32位的机器,注意这里的32位是指这台机器的机器字长是32位,机器字长32位也就意味着这台机器的CPU一次性要从内存拿32bit的数据,然后能一次性对32bit的数据进行处理(现代CPU对数据的各种处理都是基于机器字长为单位的)。

 然后如果说我不是内存对齐的,我这次想要处理变量s中的一个成员i,那这个过程可以用下图进行阐述:

这个图片告诉我们:如果说我读的数据是i,我们的CPU会从结构体变量s的起始地址处开始读数据(如果可以CPU可以选择不这么做,显然这里我们不得不这么做),一次读4Byte数据(4Byte == 32bit,图中绿框所标识的范围),显然我第一次只能拿到i的前3个字节的内容,我需要再读一遍才能完整地读取到i。也就是说: 如果内存是不对齐的,对i变量的读取CPU要读两次。而且对于变量i来说,还有一些没必要的东西被读进来了。

紧接着,我们继续探讨,如果说,我们的内存的是对齐的。那这个时候的过程则为下图所示:

显然,这时我们发现,如果说CPU要对i进行处理,它完全可以做到只需要读一次,就可以将变量i完整地读进来。

从另一个角度,当你的数据是对齐的情况下,我们内存控制器和CPU的设计呢,会更加简单。因为显然的一点,如果内存对齐的情况下,我们的CPU不需要去处理那种跨边界的数据请求;非对齐的情况下进行访问,出现跨边界的数据请求时可能需要多次内存操作才能获取到所需要的数据。这增加了我们硬件设计的复杂性并带来了潜在的延迟。

因此我们这里可也以认为是说:这种结构体存储成员的策略,是一种利用空间效率来换取时间效率的做法。

这是博主能给大家介绍的一种为什么说会存在有内存对齐的一个原因。当然呢,也有其他地方有说法是说:这种内存对齐的策略会有利于硬件平台的移植……嗯,我们不希望在这里把这个问题搞得更复杂了,因此这些其他的原因就留给感兴趣的读者朋友去探索一下吧,不再进行深入的探讨。

附二:带来的启发:

在设计结构体时,如果我既希望满足对齐,又希望所浪费的空间尽可能的少。我们应该做到:让占用空间小的成员尽量集中在一起。比如说下面两个结构体的设计,显然struct S1的设计优于struct S2的设计:

 struct S1
 {
    char c1;
    char c2;
    int i;
 };
struct S2
 {
     char c1;
     int i;
     char c2;
 };

附三:修改编译器的默认对齐数:

C语言提供了#pragma pack(对齐数)这种格式的预处理指令,来对编译器的默认对齐数进行修改。理论上,对齐数都是大于0的正整数,特别地,如果指令格式里面的对齐数不写或写成0(其他的数据类型或负数往往会被认为非法),那就表示使用默认对齐数。

我们来看一个例子:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
//先将编译器的默认对齐数设置为1:
#pragma pack(1)
struct S1
{
    char c;
    int i;
};
//再将编译器的默认对对齐数设置回来:
#pragma pack()
struct S2
{
    char c;
    int i;
};


int main()
{
    printf("sizeof(struct S1) == %zd\n", sizeof(struct S1));
    printf("sizeof(struct S2) == %zd", sizeof(struct S2));
	return 0;
}

运行截图:

我们会发现,如果编译器的默认对齐数是1,带来的感觉就是没有进行内存对齐。当然无论怎么说,这应该和我们所预期的结果(推理的结果和实际结果一致)是一致的。

附四:关于前面问题的解答:

我们在C语言自定义数据类型详解(一)——结构体类型(上)-CSDN博客里面留了一个问题:为什么说结构体的传参,一般情况下是传址优于传值。现在我们对于一个结构体它的大小是如何计算的应该都已经是小case了,回过头来思考这个问题:如果我结构体传址的话,就如果是指针的话,那该参数对于函数栈的消耗要么是4Byte,要么是8Byte(x86是4,x64是8)。而一个结构体变量的大小往往会超过8个字节(你想一个char,一个int,然后还有内存对齐,这个大小已经就有8Byte了,更不要说更复杂的结构体)。这相应地,所带来的压栈消耗也会更大。

因此我们说结构体传参,传址往往优于传值。

 

二、关于位段:

(1)搭建应用场景:

A公司最近购入了四台机器。小明作为该公司的程序员被派遣了一个任务:要求建立一个数据结构用于维护四台机器的各自状态,这其中就包括了机器各自的id(0,1,2,3),机器的运行时间time,标识机器是否正常运行的状态指针state……这些可能就是作为维护人员,我们需要去关心的一些参数:

于是小明据此声明了下面这种结构,用来维护工厂里的这四台机器:

struct Machine
{
    int id;     //唯一标识一台机器的id
    int time;   //机器的运行时间
    int state; //机器目前的状态:1表示正常运行,0表示不正常
}

但是小明后来在使用过程中也注意到了一个问题,在这个结构体中,有一些成员的取值是非常有限的,比如说一台机器的id只有0,1,2,3四种可能的结果,一个机器的状态也就正常和故障两种。

而对于这样的成员变量,我们用int来存,嗯……可以是可以,但是我实际上能用到的也就2个二进制位而已(00:0,01:1,10:2,11:3)。也就是我当前的这种结构,id中有30bit都是无效的。

有小伙伴说用char,当然啦,这也行,但是有没有其他的方法呢?OK,这里跟着博主的步伐来认识一下我们接下来的主角——位段。

(2)位段的概念性理解:

在某些应用场景中,为了尽可能地节省空间,我们的C语言结构体一般都有能够实现位段的能力。

位段的声明和结构体非常类似,但是有两个本质上的区别:

  1. 位段的成员只能是int,unsigned int 或signed int。C99之后支持了更多的数据类型,但是基本上就是int,char这样的数据类型。
  2. 在位段的成员名的后面,往往有一个冒号和一个数字,用来表示该成员变量的大小。以bit为单位。

也就是说,位段的成员虽然冠以int自称,但是它的实际大小往往低于int类型的4Byte。我们可以来看一个例子:

//我们这里在位段的成员名前面都加了下划线_,仅仅是为了便于区分它和一般的结构体成员变量,即这仅仅是个命名习惯罢了:
struct S
{
    int _a : 2;  //_a是位段成员,占2  bit位
    int _b : 5;  //_b是位段成员,占5  bit位
    int _c : 10; //_c是位段成员,占10 bit位
    int d;
};

(3)位段的内存分配:

对于位段成员的内存分配,C语言也没有提供具体的标准,但是一个大致的方向是:当你定义了一个位段成员时,它会先给你1Byte或4Byte空间(这一般取决于你前面的类型写的是int还是char),然后你不够了,编译器会继续再给你1Byte或4Byte空间。

什么意思呢?就拿我们前面定义的结构体来说,我们先把它拿过来:

#include<stdio.h>
//我们这里在位段的成员名前面都加了下划线_,仅仅是为了便于区分它和一般的结构体成员变量,即这仅仅是个命名习惯罢了:
struct S
{
    int _a : 2;  //_a是位段成员,占2  bit位
    int _b : 5;  //_b是位段成员,占5  bit位
    int _c : 10; //_c是位段成员,占10 bit位
    int d;
};

int main()
{
    printf("sizeof(struct S) == %zd",sizeof(struct S));
    return 0;
}

首先编译器看你需要一个int类型的成员变量,于是它会先给你开辟4Byte空间。显然4Byte = 32bit,这对于_a,_b,_c显然是够用了的。因此_a,_b,_c会公用这4byte的空间。加上之后的int类型的b变量,这个结构体最终的大小大概就是8Byte左右。

显然这比你直接用4个int所需要的内存少多了。如图所示:

这就是一种简单地分析位段大小的一种方式。这里面其实还有很多的细节,如图所示:

具体到内部,_a要2bit,这2bit开辟,是从左往右,还是从右往左是C语言标准没有规定的。

另外,如果说是这样的:

#include<stdio.h>
//我们这里在位段的成员名前面都加了下划线_,仅仅是为了便于区分它和一般的结构体成员变量,即这仅仅是个命名习惯罢了:
struct S
{
    int _a : 15;  //_a是位段成员,占15 bit位
    int _b : 12;  //_b是位段成员,占12 bit位
    int _c : 10;  //_c是位段成员,占10 bit位
};

这里我们当然知道说这个结构体是8byte,然后_a和_b用了前4Byte的27bit位。问题是来给_c分配空间的时候:我们知道前面4Byte用了27bit了(_a和_b),它还有5bit没有被使用。那这5bit会不会给到_c,然后再从后面的4Byte拿出5bit,凑成最终的10bit呢。

还是说这前面4Byte遗留下的5bit不要了,_c的10bit全部在后面的4Byte里面。这件事情也是我们C语言标准所没有规定的事情。

换句话说,位段内部在给成员分配空间时,它的不确定性因素实在是太多了。这带来的直接问题便是:位段的跨平台性非常差!!!因为没有标准,每个编译器的厂家都是自己的那一套。因此我们实际中用位段其实用得非常非常少,这是希望大家注意到的一点。

博主在这里介绍位段的目的,也仅仅只是希望大家以后在纯C里面看到种定义结构体成员的方式,能够看得懂。

 

至此,关于结构体所有相关的周边知识就介绍完啦,我们下期再见!🙂

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/961910.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【ComfyUI专栏】如何使用Git命令行安装非Manager收录节点

当前的ComfyUI的收录的自定义节点很多&#xff0c;但是有些节点属于新出来&#xff0c;或者他的应用没有那么广泛&#xff0c;Manager管理节点 有可能没有收录到&#xff0c;这时候 如果我们需要安装需要怎么办呢&#xff1f;这就涉及到我们自己安装这些节点了。例如下面的内容…

【Block总结】PKI 模块,无膨胀多尺度卷积,增强特征提取的能力|即插即用

论文信息 标题: Poly Kernel Inception Network for Remote Sensing Detection 作者: Xinhao Cai, Qiuxia Lai, Yuwei Wang, Wenguan Wang, Zeren Sun, Yazhou Yao 论文链接&#xff1a;https://arxiv.org/pdf/2403.06258 代码链接&#xff1a;https://github.com/NUST-Mac…

[OO ALV] OO ALV 基础显示

程序代码 REPORT z437_test_2025.DATA gt_spfli TYPE STANDARD TABLE OF spfli. " 内表DATA go_alv TYPE REF TO cl_gui_alv_grid. " 创建和管理ALV表格DATA gs_layout TYPE lvc_s_layo. " 存储ALV表格的布局信息*---------------------…

jQuery小游戏(二)

jQuery小游戏&#xff08;二&#xff09; 今天是新年的第二天&#xff0c;本人在这里祝大家&#xff0c;新年快乐&#xff0c;万事胜意&#x1f495; 紧接jQuery小游戏&#xff08;一&#xff09;的内容&#xff0c;我们开始继续往下咯&#x1f61c; 游戏中使用到的方法 key…

Linux的常用指令的用法

目录 Linux下基本指令 whoami ls指令&#xff1a; 文件&#xff1a; touch clear pwd cd mkdir rmdir指令 && rm 指令 man指令 cp mv cat more less head tail 管道和重定向 1. 重定向&#xff08;Redirection&#xff09; 2. 管道&#xff08;Pipes&a…

蓝桥杯之c++入门(一)【C++入门】

目录 前言5. 算术操作符5.1 算术操作符5.2 浮点数的除法5.3 负数取模5.4 数值溢出5.5 练习练习1&#xff1a;计算 ( a b ) ⋆ c (ab)^{\star}c (ab)⋆c练习2&#xff1a;带余除法练习3&#xff1a;整数个位练习4&#xff1a;整数十位练习5&#xff1a;时间转换练习6&#xff…

51单片机开发:定时器中断

目标&#xff1a;利用定时器中断&#xff0c;每隔1s开启/熄灭LED1灯。 外部中断结构图如下图所示&#xff0c;要使用定时器中断T0&#xff0c;须开启TE0、ET0。&#xff1a; 系统中断号如下图所示&#xff1a;定时器0的中断号为1。 定时器0的工作方式1原理图如下图所示&#x…

【设计测试用例自动化测试性能测试 实战篇】

&#x1f308;个人主页&#xff1a;努力学编程’ ⛅个人推荐&#xff1a; c语言从初阶到进阶 JavaEE详解 数据结构 ⚡学好数据结构&#xff0c;刷题刻不容缓&#xff1a;点击一起刷题 &#x1f319;心灵鸡汤&#xff1a;总有人要赢&#xff0c;为什么不能是我呢 设计测试用例…

指针(C语言)从0到1掌握指针,为后续学习c++打下基础

目录 一&#xff0c;指针 二&#xff0c;内存地址和指针 1&#xff0c;什么是内存地址 2&#xff0c;指针在不同系统下所占内存 三&#xff0c;指针的声明和初始化以及类型 1,指针的声明 2,指针 的初始化 1&#xff0c; 初始化方式优点及适用场景 4,指针的声明初始化类型…

利用Redis实现数据缓存

目录 1 为啥要缓存捏&#xff1f; 2 基本流程&#xff08;以查询商铺信息为例&#xff09; 3 实现数据库与缓存双写一致 3.1 内存淘汰 3.2 超时剔除&#xff08;半自动&#xff09; 3.3 主动更新&#xff08;手动&#xff09; 3.3.1 双写方案 3.3.2 读写穿透方案 3.3.…

MySQL(高级特性篇) 14 章——MySQL事务日志

事务有4种特性&#xff1a;原子性、一致性、隔离性和持久性 事务的隔离性由锁机制实现事务的原子性、一致性和持久性由事务的redo日志和undo日志来保证&#xff08;1&#xff09;REDO LOG称为重做日志&#xff0c;用来保证事务的持久性&#xff08;2&#xff09;UNDO LOG称为回…

S4 HANA明确税金本币和外币之间转换汇率确定(OBC8)

本文主要介绍在S4 HANA OP中明确明确税金本币和外币之间转换汇率确定(OBC8)相关设置。具体请参照如下内容&#xff1a; 明确税金本币和外币之间转换汇率确定(OBC8) 以上配置&#xff0c;我们可以根据不同公司代码所配置的使用不同的汇率来对税金外币和本币之间进行换算。来针对…

SpringBoot 日志与配置文件

SpringBoot 配置文件格式 Properties 格式 Component ConfigurationProperties(prefix "person") //和配置文件person前缀的所有配置进行绑定 Data public class Person {private String name;private Integer age;private Date birthDay;private Boolean like;pr…

oracl:数据查询语言DQL

SQL&#xff08;Structured Query Language&#xff0c;结构化查询语言&#xff09;是一种用于管理和操作关系数据库的标准编程语言。 sql分类: 基本查询语法 数据查询语言&#xff08;DQL - Data Query Language&#xff09; 查询的关键词 select where对查询结果进行过滤…

Hive:窗口函数(1)

窗口函数 窗口函数OVER()用于定义一个窗口&#xff0c;该窗口指定了函数应用的数据范围 对窗口数据进行分区 partition by 必须和over () 一起使用, distribute by经常和sort by 一起使用,可以不和over() 一起使用.DISTRIBUTE BY决定了数据如何分布到不同的Reducer上&#xf…

多目标优化策略之一:非支配排序

多目标优化策略中的非支配排序是一种关键的技术,它主要用于解决多目标优化问题中解的选择和排序问题,确定解集中的非支配解(也称为Pareto解)。 关于什么是多目标优化问题,可以查看我的文章:改进候鸟优化算法之五:基于多目标优化的候鸟优化算法(MBO-MO)-CSDN博客 多目…

神经网络和深度学习

应用 类型 为什么近几年飞速发展 数据增长&#xff0c;算力增长&#xff0c;算法革新 逻辑回归 向量化 浅层神经网络(Shallow neural network) 单条训练数据前向传播计算表达式 batch训练数据前向传播计算表达式 反向传播计算表达式 参数随机初始化 不能全部设为0 原因是同一…

生成模型:扩散模型(DDPM, DDIM, 条件生成)

扩散模型的理论较为复杂&#xff0c;论文公式与开源代码都难以理解。现有的教程大多侧重推导公式。为此&#xff0c;本文通过精简代码&#xff08;约300行&#xff09;&#xff0c;从代码运行角度讲解扩散模型。 本文包括扩散模型的3项技术复现&#xff1a; 1.DDPM (Denoising…

浅谈网络 | 容器网络之Flannel

目录 云原生网络架构深度解构&#xff1a;Flannel的设计哲学与实现机制Flannel架构解析&#xff1a;三层核心设计原则UDP模式&#xff08;用户态隧道&#xff09;VXLAN模式&#xff08;内核态隧道&#xff09;Host-GW模式&#xff08;直连路由&#xff09; 生产环境架构选型与调…

新鲜速递:DeepSeek-R1开源大模型本地部署实战—Ollama + MaxKB 搭建RAG检索增强生成应用

在AI技术快速发展的今天&#xff0c;开源大模型的本地化部署正在成为开发者们的热门实践方向。最火的莫过于吊打OpenAI过亿成本的纯国产DeepSeek开源大模型&#xff0c;就在刚刚&#xff0c;凭一己之力让英伟达大跌18%&#xff0c;纳斯达克大跌3.7%&#xff0c;足足是给中国AI产…