计算payload长度c语言,C语言0长度数组(可变数组/柔性数组)详解

1 零长度数组概念

众所周知, GNU/GCC 在标准的 C/C++ 基础上做了有实用性的扩展, 零长度数组(Arrays of Length Zero) 就是其中一个知名的扩展.

多数情况下, 其应用在变长数组中, 其定义如下struct Packet{ int state; int len; char cData[0]; //这里的0长结构体就为变长结构体提供了非常好的支持};1

2

3

4

5

6

首先对 0长度数组, 也叫柔性数组 做一个解释 :用途 : 长度为0的数组的主要用途是为了满足需要变长度的结构体

用法 : 在一个结构体的最后, 申明一个长度为0的数组, 就可以使得这个结构体是可变长的. 对于编译器来说, 此时长度为0的数组并不占用空间, 因为数组名本身不占空间, 它只是一个偏移量, 数组名这个符号本身代表了一个不可修改的地址常量(注意 : 数组名永远都不会是指针!), 但对于这个数组的大小, 我们可以进行动态分配

注意 :如果结构体是通过calloc、malloc或 者new等动态分配方式生成,在不需要时要释放相应的空间。

优点 :比起在结构体中声明一个指针变量、再进行动态分 配的办法,这种方法效率要高。因为在访问数组内容时,不需要间接访问,避免了两次访存。

缺点 :在结构体中,数组为0的数组必须在最后声明,使 用上有一定限制。

对于编译器而言, 数组名仅仅是一个符号, 它不会占用任何空间, 它在结构体中, 只是代表了一个偏移量, 代表一个不可修改的地址常量!

2 0长度数组的用途

我们设想这样一个场景, 我们在网络通信过程中使用的数据缓冲区, 缓冲区包括一个len字段和data字段, 分别标识数据的长度和传输的数据, 我们常见的有几种设计思路定长数据缓冲区, 设置一个足够大小 MAX_LENGTH 的数据缓冲区

设置一个指向实际数据的指针, 每次使用时, 按照数据的长度动态的开辟数据缓冲区的空间.

我们从实际场景中应用的设计来考虑他们的优劣. 主要考虑的有, 缓冲区空间的开辟, 释放和访问.

2.1 定长包(开辟空间, 释放, 访问)

比如我要发送 1024 字节的数据, 如果用定长包, 假设定长包的长度 MAX_LENGTH 为 2048, 就会浪费 1024 个字节的空间, 也会造成不必要的流量浪费.数据结构定义// 定长缓冲区struct max_buffer{ int len; char data[MAX_LENGTH];};1

2

3

4

5

6数据结构大小

考虑对齐, 那么数据结构的大小 >= sizeof(int) + sizeof(char) * MAX_LENGTH

由于考虑到数据的溢出, 变长数据包中的 data 数组长度一般会设置得足够长足以容纳最大的数据, 因此 max_buffer 中的 data 数组很多情况下都没有填满数据, 因此造成了浪费数据包的构造

假如我们要发送 CURR_LENGTH = 1024 个字节, 我们如何构造这个数据包呢:

一般来说, 我们会返回一个指向缓冲区数据结构 max_buffer 的指针./// 开辟 if ((mbuffer = (struct max_buffer *)malloc(sizeof(struct max_buffer))) != NULL) { mbuffer->len = CURR_LENGTH; memcpy(mbuffer->data, "Hello World", CURR_LENGTH); printf("%d, %s\n", mbuffer->len, mbuffer->data); }1

2

3

4

5

6

7

8

9访问

这段内存要分两部分使用

前部分 4 个字节 p->len, 作为包头(就是多出来的那部分),这个包头是用来描述紧接着包头后面的数据部分的长度,这里是 1024, 所以前四个字节赋值为 1024 (既然我们要构造不定长数据包,那么这个包到底有多长呢,因此,我们就必须通过一个变量来表明这个数据包的长度,这就是len的作用),

而紧接其后的内存是真正的数据部分, 通过 p->data, 最后, 进行一个 memcpy() 内存拷贝, 把要发送的数据填入到这段内存当中释放

那么当使用完毕释放数据的空间的时候, 直接释放就可以了/// 销毁 free(mbuffer); mbuffer = NULL;1

2

3小结使用定长数组, 作为数据缓冲区, 为了避免造成缓冲区溢出, 数组的大小一般设为足够的空间 MAX_LENGTH, 而实际使用过程中, 达到 MAX_LENGTH 长度的数据很少, 那么多数情况下, 缓冲区的大部分空间都是浪费掉的.

但是使用过程很简单, 数据空间的开辟和释放简单, 无需程序员考虑额外的操作

2.2 指针数据包(开辟空间, 释放, 访问)

如果你将上面的长度为 MAX_LENGTH 的定长数组换为指针, 每次使用时动态的开辟 CURR_LENGTH 大小的空间, 那么就避免造成 MAX_LENGTH - CURR_LENGTH 空间的浪费, 只浪费了一个指针域的空间.数据包定义struct point_buffer{ int len; char *data;};1

2

3

4

5数据结构大小

考虑对齐, 那么数据结构的大小 >= sizeof(int) + sizeof(char *)空间分配

但是也造成了使用在分配内存时,需采用两步// ===================== // 指针数组 占用-开辟-销毁 // ===================== /// 占用 printf("the length of struct test3:%d\n",sizeof(struct point_buffer)); /// 开辟 if ((pbuffer = (struct point_buffer *)malloc(sizeof(struct point_buffer))) != NULL) { pbuffer->len = CURR_LENGTH; if ((pbuffer->data = (char *)malloc(sizeof(char) * CURR_LENGTH)) != NULL) { memcpy(pbuffer->data, "Hello World", CURR_LENGTH); printf("%d, %s\n", pbuffer->len, pbuffer->data); } }1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17首先, 需为结构体分配一块内存空间;

其次再为结构体中的成员变量分配内存空间.

这样两次分配的内存是不连续的, 需要分别对其进行管理. 当使用长度为的数组时, 则是采用一次分配的原则, 一次性将所需的内存全部分配给它.释放

相反, 释放时也是一样的./// 销毁 free(pbuffer->data); free(pbuffer); pbuffer = NULL;1

2

3

4小结使用指针结果作为缓冲区, 只多使用了一个指针大小的空间, 无需使用 MAX_LENGTH 长度的数组, 不会造成空间的大量浪费.

但那是开辟空间时, 需要额外开辟数据域的空间, 施放时候也需要显示释放数据域的空间, 但是实际使用过程中, 往往在函数中开辟空间, 然后返回给使用者指向 struct point_buffer 的指针, 这时候我们并不能假定使用者了解我们开辟的细节, 并按照约定的操作释放空间, 因此使用起来多有不便, 甚至造成内存泄漏

2.3 变长数据缓冲区(开辟空间, 释放, 访问)

定长数组使用方便, 但是却浪费空间, 指针形式只多使用了一个指针的空间, 不会造成大量空间分浪费, 但是使用起来需要多次分配, 多次释放, 那么有没有一种实现方式能够既不浪费空间, 又使用方便的呢?

GNU C 的0长度数组, 也叫变长数组, 柔性数组就是这样一个扩展. 对于0长数组的这个特点,很容易构造出变成结构体,如缓冲区,数据包等等:数据结构定义// 0长度数组struct zero_buffer{ int len; char data[0];};1

2

3

4

5

6数据结构大小

这样的变长数组常用于网络通信中构造不定长数据包, 不会浪费空间浪费网络流量, 因为char data[0]; 只是个数组名, 是不占用存储空间的,

即 sizeof(struct zero_buffer) = sizeof(int)开辟空间

那么我们使用的时候, 只需要开辟一次空间即可/// 开辟 if ((zbuffer = (struct zero_buffer *)malloc(sizeof(struct zero_buffer) + sizeof(char) * CURR_LENGTH)) != NULL) { zbuffer->len = CURR_LENGTH; memcpy(zbuffer->data, "Hello World", CURR_LENGTH); printf("%d, %s\n", zbuffer->len, zbuffer->data); }1

2

3

4

5

6

7

8

9释放空间

释放空间也是一样的, 一次释放即可/// 销毁 free(zbuffer); zbuffer = NULL;1

2

3

2.4 总结// zero_length_array.c#include #include #define MAX_LENGTH 1024#define CURR_LENGTH 512// 0长度数组struct zero_buffer{ int len; char data[0];}__attribute((packed));// 定长数组struct max_buffer{ int len; char data[MAX_LENGTH];}__attribute((packed));// 指针数组struct point_buffer{ int len; char *data;}__attribute((packed));int main(void){ struct zero_buffer *zbuffer = NULL; struct max_buffer *mbuffer = NULL; struct point_buffer *pbuffer = NULL; // ===================== // 0长度数组 占用-开辟-销毁 // ===================== /// 占用 printf("the length of struct test1:%d\n",sizeof(struct zero_buffer)); /// 开辟 if ((zbuffer = (struct zero_buffer *)malloc(sizeof(struct zero_buffer) + sizeof(char) * CURR_LENGTH)) != NULL) { zbuffer->len = CURR_LENGTH; memcpy(zbuffer->data, "Hello World", CURR_LENGTH); printf("%d, %s\n", zbuffer->len, zbuffer->data); } /// 销毁 free(zbuffer); zbuffer = NULL; // ===================== // 定长数组 占用-开辟-销毁 // ===================== /// 占用 printf("the length of struct test2:%d\n",sizeof(struct max_buffer)); /// 开辟 if ((mbuffer = (struct max_buffer *)malloc(sizeof(struct max_buffer))) != NULL) { mbuffer->len = CURR_LENGTH; memcpy(mbuffer->data, "Hello World", CURR_LENGTH); printf("%d, %s\n", mbuffer->len, mbuffer->data); } /// 销毁 free(mbuffer); mbuffer = NULL; // ===================== // 指针数组 占用-开辟-销毁 // ===================== /// 占用 printf("the length of struct test3:%d\n",sizeof(struct point_buffer)); /// 开辟 if ((pbuffer = (struct point_buffer *)malloc(sizeof(struct point_buffer))) != NULL) { pbuffer->len = CURR_LENGTH; if ((pbuffer->data = (char *)malloc(sizeof(char) * CURR_LENGTH)) != NULL) { memcpy(pbuffer->data, "Hello World", CURR_LENGTH); printf("%d, %s\n", pbuffer->len, pbuffer->data); } } /// 销毁 free(pbuffer->data); free(pbuffer); pbuffer = NULL; return EXIT_SUCCESS;}1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

116589981_2_20171119104117384长度为0的数组并不占有内存空间, 而指针方式需要占用内存空间.

对于长度为0数组, 在申请内存空间时, 采用一次性分配的原则进行; 对于包含指针的结构体, 才申请空间时需分别进行, 释放时也需分别释放.

对于长度为的数组的访问可采用数组方式进行

3 GNU Document中 变长数组的支持

在 C90 之前, 并不支持0长度的数组, 0长度数组是 GNU C 的一个扩展, 因此早期的编译器中是无法通过编译的

对于 GNU C 增加的扩展, GCC 提供了编译选项来明确的标识出他们

1、-pedantic 选项,那么使用了扩展语法的地方将产生相应的警告信息

2、-Wall 使用它能够使GCC产生尽可能多的警告信息

3、-Werror, 它要求GCC将所有的警告当成错误进行处理// 1.c#include #include int main(void){ char a[0]; printf("%ld", sizeof(a)); return EXIT_SUCCESS;}1

2

3

4

5

6

7

8

9

10

11

我们来编译gcc 1.c -Wall # 显示所有警告#none warning and errorgcc 1.c -Wall -pedantic # 对GNU C的扩展显示警告1.c: In function ‘main’:1.c:7: warning: ISO C forbids zero-size array ‘a’gcc 1.c -Werror -Wall -pedantic # 显示所有警告同时GNU C的扩展显示警告, 将警告用error显示cc1: warnings being treated as errors1.c: In function ‘main’:1.c:7: error: ISO C forbids zero-size array ‘a’1

2

3

4

5

6

7

8

9

10

11

12

116589981_3_20171119104117712

0长度数组其实就是灵活的运用的数组指向的是其后面的连续的内存空间struct buffer{ int len; char data[0];};1

2

3

4

5

在早期没引入0长度数组的时候, 大家是通过定长数组和指针的方式来解决的, 但是定长数组定义了一个足够大的缓冲区, 这样使用方便, 但是每次都造成空间的浪费

指针的方式, 要求程序员在释放空间是必须进行多次的free操作, 而我们在使用的过程中往往在函数中返回了指向缓冲区的指针, 我们并不能保证每个人都理解并遵从我们的释放方式

所以 GNU 就对其进行了0长度数组的扩展. 当使用data[0]的时候, 也就是0长度数组的时候,0长度数组作为数组名, 并不占用存储空间.

在C99之后,也加了类似的扩展,只不过用的是 char payload[]这种形式(所以如果你在编译的时候确实需要用到-pedantic参数,那么你可以将char payload[0]类型改成char payload[], 这样就可以编译通过了,当然你的编译器必须支持C99标准的,如果太古老的编译器,那可能不支持了)// 2.c payload#include #include struct payload{ int len; char data[];};int main(void){ struct payload pay; printf("%ld", sizeof(pay)); return EXIT_SUCCESS;}1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

使用 -pedantic 编译后, 不出现警告, 说明这种语法是 C 标准的gcc 2.c -pedantic -std=c991

116589981_4_20171119104117915

所以结构体的末尾, 就是指向了其后面的内存数据。因此我们可以很好的将该类型的结构体作为数据报文的头格式,并且最后一个成员变量,也就刚好是数据内容了.

GNU手册还提供了另外两个结构体来说明,更容易看懂意思:struct f1 { int x; int y[];} f1 = { 1, { 2, 3, 4 } };struct f2 { struct f1 f1; int data[3];} f2 = { { 1 }, { 5, 6, 7 } };1

2

3

4

5

6

7

8

9

我把f2里面的2,3,4改成了5,6,7以示区分。如果你把数据打出来。即如下的信息:f1.x = 1f1.y[0] = 2f1.y[1] = 3f1.y[2] = 41

2

3

4

也就是f1.y指向的是{2,3,4}这块内存中的数据。所以我们就可以轻易的得到,f2.f1.y指向的数据也就是正好f2.data的内容了。打印出来的数据:f2.f1.x = 1f2.f1.y[0] = 5f2.f1.y[1] = 6f2.f1.y[2] = 71

2

3

4

如果你不是很确认其是否占用空间. 你可以用sizeof来计算一下。就可以知道sizeof(struct f1)=4,也就是int y[]其实是不占用空间的。但是这个0长度的数组,必须放在结构体的末尾。如果你没有把它放在末尾的话。编译的时候,会有如下的错误:main.c:37:9: error: flexible array member not at end of struct int y[]; ^1

2

3

到这边,你可能会有疑问,如果将struct f1中的int y[]替换成int *y,又会是如何?这就涉及到数组和指针的问题了. 有时候吧,这两个是一样的,有时候又有区别。

首先要说明的是,支持0长度数组的扩展,重点在数组,也就是不能用int *y指针来替换。sizeof的长度就不一样了。把struct f1改成这样:struct f3 { int x; int *y;};1

2

3

4

在32/64位下, int均是4个字节, sizeof(struct f1)=4,而sizeof(struct f3)=16

因为 int *y 是指针, 指针在64位下, 是64位的, sizeof(struct f3) = 16, 如果在32位环境的话, sizeof(struct f3) 则是 8 了, sizeof(struct f1) 不变. 所以 int *y 是不能替代 int y[] 的.

代码如下// 3.c#include #include struct f1 { int x; int y[];} f1 = { 1, { 2, 3, 4 } };struct f2 { struct f1 f1; int data[3];} f2 = { { 1 }, { 5, 6, 7 } };struct f3{ int x; int *y;};int main(void){ printf("sizeof(f1) = %d\n", sizeof(struct f1)); printf("sizeof(f2) = %d\n", sizeof(struct f2)); printf("szieof(f3) = %d\n\n", sizeof(struct f3)); printf("f1.x = %d\n", f1.x); printf("f1.y[0] = %d\n", f1.y[0]); printf("f1.y[1] = %d\n", f1.y[1]); printf("f1.y[2] = %d\n", f1.y[2]); printf("f2.f1.x = %d\n", f1.x); printf("f2.f1.y[0] = %d\n", f2.f1.y[0]); printf("f2.f1.y[1] = %d\n", f2.f1.y[1]); printf("f2.f1.y[2] = %d\n", f2.f1.y[2]); return EXIT_SUCCESS;}1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

116589981_5_2017111910411825

4 0长度数组的其他特征

4.1 为什么0长度数组不占用存储空间

0长度数组与指针实现有什么区别呢, 为什么0长度数组不占用存储空间呢?

其实本质上涉及到的是一个C语言里面的数组和指针的区别问题. char a[1]里面的a和char *b的b相同吗?

《 Programming Abstractions in C》(Roberts, E. S.,机械工业出版社,2004.6)82页里面说“arr is defined to be identical to &arr[0]”.

也就是说,char a[1]里面的a实际是一个常量,等于&a[0]。而char *b是有一个实实在在的指针变量b存在。 所以,a=b是不允许的,而b=a是允许的。 两种变量都支持下标式的访问,那么对于a[0]和b[0]本质上是否有区别?我们可以通过一个例子来说明。

参见如下两个程序 gdb_zero_length_array.c 和 gdb_zero_length_array.c// gdb_zero_length_array.c#include #include struct str{ int len; char s[0];};struct foo{ struct str *a;};int main(void){ struct foo f = { NULL }; printf("sizeof(struct str) = %d\n", sizeof(struct str)); printf("before f.a->s.\n"); if(f.a->s) { printf("before printf f.a->s.\n"); printf(f.a->s); printf("before printf f.a->s.\n"); } return EXIT_SUCCESS;}1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

116589981_6_20171119104118212// gdb_pzero_length_array.c#include #include struct str{ int len; char *s;};struct foo{ struct str *a;};int main(void){ struct foo f = { NULL }; printf("sizeof(struct str) = %d\n", sizeof(struct str)); printf("before f.a->s.\n"); if (f.a->s) { printf("before printf f.a->s.\n"); printf(f.a->s); printf("before printf f.a->s.\n"); } return EXIT_SUCCESS;}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

go语言调用c 的头文件 so,golang 学习(10): 使用go语言调用c语言的so动态库-Go语言中文社区...

一、前言最近在学习go,因为需要调用c语言打包成的so动态库里面的方法,避免自己再去造轮子,所以想直接使用golang调用so,但是参考了其他博客大佬写的,我每一步原封不动的写下来,结果都是一堆错误&#xff0c…

android 开机动画 渐变,[Parallax Animation]实现知乎 Android 客户端启动页视差滚动效果...

前言Parallax Scrolling (视差滚动),是一种常见的动画效果。视差一词来源于天文学,但在日常生活中也有它的身影。在疾驰的动车上看风景时,会发现越是离得近的,相对运动速度越快,而远处的山川河流只是缓慢的移动着&…

python可以做计量分析吗_技术分享 - python数据分析(2)——数据特征分析(上)...

1 分布分析 分布分析能揭示数据的分布特征和分布类型。对于定量数据,欲了解其分布形式是对称的还是非对称的,发现某些特大或特小的可疑值,可通过绘制频率分布表、绘制频率分布直方图、绘制茎叶图进行直观地分析;对于定性分类数据&…

matlab的7.3版本是什么_乐建工程宝V6.3版本升级说明公告

尊敬的乐建工程宝客户:您好!为了给客户提供更加优质的产品和服务,我司已于2019年11月20日开始乐建工程宝V6.3版本升级服务。目前,Android系统各应用市场已基本审核完毕,iOS系统已上传AppStore,目前苹果官方…

origin设置不同区域的颜色_[测试狗]Origin入门教程(二十四):效率翻倍小技巧——修改默认字体...

在使用Origin的时候,对于每次绘图都需要更改字体觉得很麻烦,因为Origin默认的字体为Arial,但是我们常用的字体一般为Times New Roman,在下拉框的很底部,每次更改都很浪费时间。那为什么不把他设置成默认字体呢&#xf…

cgi web 调用多次启动_全面了解CGI、FastCGI、PHPFPM

一、抛个砖1、Web Server传递数据的方法正式说CGI之前,先来了解一下Web Server传递数据的另外一种方法:PHP Module加载方式。相信都会想起Apache吧,初学php时,在windows上安装完php和Apache之后,为了让Apache能够解析p…

mysql 按月和年累加_广西柳州市市场监管局公布市2020年11月(第一批)电梯按需维保试点名单...

中国质量新闻网讯 根据《柳州市改进电梯维护保养模式试点工作方案》,近日,广西柳州市市场监管局公布柳州市首批按需维保试点电梯名单,冠亚蓝湾国际小区和南庆安置小区共46台电梯成为首批试点电梯,标志着柳州市全面启动了按需维保改…

上传 mp4 格式判断_视频如何转换成通用的MP4格式?按下这个键,10秒就能搞定...

我们在网上下载视频的时候,有很多的视频都是无法播放的,或者是需要特定的播放器才可以播放。其实,只要把这些视频的格式转换成通用的MP4格式即可。如果你还不知道怎么转换视频格式,下面就教大家两个小方法,百试百灵。一…

android不能在主线程,安卓开发:主线程真的不能做UI操作吗?这一点很多程序员都没想到...

只要参与过安卓项目开发一两年的朋友们应该清楚,为了避免UI渲染出现异常安卓框架限制UI操作只能在主线程中进行,如果贸然在子线程做了UI操作结果会怎样?我们随便写下了如下测试代码。不出意外的话,代码执行报错抛出了名为CalledFr…

c++注释快捷键_Jupyter Notebook amp; Lab快捷键大全

Jupyter有两种模式,命令模式和编辑模式,分别有不同的快捷键。编辑模式(按键 Enter 切换):可以往单元中键入代码或文本,此时单元格被蓝色的框线包围,且命令模式下的快捷键不生效; 命令模式 (按键 Esc 开启)&…

swot分析法案例_项目型销售案例剖析的五大步骤

我们的案例分析方式是根据哈佛大学与中欧国际工商学院的案例分析方法来总结出我们的模式的。这种分析方法包括两种互相关联和依赖的方面。第一方面,就是要对所指定的将供集体讨论的案例,做出深刻而有意义的分析,包括找出案例所描述的情景中存…

定时器和promise_从Promise链理解EventLoop

面试题new Promise(resolve > { setTimeout(()>{ console.log(666); new Promise(resolve > { resolve(); }) .then(() > {console.log(777);}) }) resolve(); }) .then(() > { new Promise(resolve > { resolve(); …

ugui源码_UGUI整体解决方案基础篇(Unity 2019)

课程介绍:本课程是UGUI系列课程的第一篇:基础篇主要是讲解UGUI的基础组件及接口的使用方法,目前UGUI是unity最常用的UI系统,这部分基础只是是每个同学都应该掌握的,这里我就是简单的讲解了用法,大家对UGUI熟…

电脑会显示android,怎么在电脑上显示、操作安卓手机

想要在电脑上显示、操作安卓手机,该怎么办,那么怎么在电脑上显示、操作安卓手机的呢?下面是学习啦小编收集整理的怎么在电脑上显示、操作安卓手机,希望对大家有帮助~~在电脑上显示、操作安卓手机的方法工具/原料windows操作系统安卓手机电脑…

git version是什么软件_Deepin 15.11 安装 ZoneMinder 视频监控软件

Zoneminder是一款开源的视频监控软件,可以很方便的连接ip摄像头。因计划将家中的监控摄像头引入NAS,在一台deepin系统的笔记本是先进行了测试。UBUNTU和debian系统都是很容易安装这个软件的。未来在NAS上用docker启动一个专门的zoneminder,do…

android 通知历史,Android P新特性:追踪应用通知历史

原标题:Android P新特性:追踪应用通知历史IT之家3月9日消息 不久前,谷歌已经正式推出了首个Android P开发者预览版,包含了许多新特性。对此,IT之家也进行了一系列报道。该系统的新特性也正在不断被发现。例如最新消息显…

文件另存为时名称会改变_易经:人处在困境时,不要焦虑,改变固定习惯,就会迎来转机...

我读《易经》,悟到一些规律:人的一生,起起落落,时而顺利,时而受困,都是正常现象,没有必要把困难和压力看得太重。人处在困境时,不要焦虑,只要改变你的固定习惯&#xff0…

鸿蒙系统替代iOS,华为横空出世!鸿蒙系统,能否替代安卓IOS?

原标题:华为横空出世!鸿蒙系统,能否替代安卓IOS?从长远来看,华为主推鸿蒙系统是必然的选择。毕竟安卓系统为谷歌的,而由于美国限制,让华为格外被动。命运掌握在自己手里,才有足够的话…

热门搜索怎么实现_三个步骤教你学会,搜索引擎霸屏技术!

做好SEO就要了解搜索引擎霸屏技术,它是在百度中搜索关键字来检索信息。整个画面的推荐都是你的内容。那么客户点击你的可能性就会增加!那么搜索引擎霸屏技术这么好,那要如何做到呢?1.要想成为霸屏,第一步要选择好的关键…

html字体如何设置垂直居中显示,css文字水平垂直居中怎么设置?

css文字水平垂直居中怎么设置?下面本篇文章就来给大家介绍使用CSS设置文字水平居中和垂直居中的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。1、文字水平居中在CSS中想要让文字水平居中,可以使用text-a…