C语言数组长度为零的妙用

李肖遥 2025-01-15 22:15
    关注、星标公众号,直达精彩内容

零长度数组概念:

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

多数情况下, 其应用在变长数组中, 其定义如下:

struct Packet
{

    int state;
  int len;
  char cData[0]; //这里的0长结构体就为变长结构体提供了非常好的支持
};

首先对 0 长度数组, 也叫柔性数组,做一个解释 :

  • 用途 : 长度为0的数组的主要用途是为了满足需要变长度的结构体;

  • 用法 : 在一个结构体的最后,声明一个长度为 0 的数组, 就可以使得这个结构体是可变长的. 对于编译器来说, 此时长度为 0 的数组并不占用空间, 因为数组名本身不占空间, 它只是一个偏移量, 数组名这个符号本身代表了一个不可修改的地址常量

(注意 : 数组名永远都不会是指针!), 但对于这个数组的大小, 我们可以进行动态分配。

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

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

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

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

0 长度数组的用途:

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

  • 定长数据缓冲区, 设置一个足够大小 MAX_LENGTH 的数据缓冲区

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

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

1、定长包(开辟空间, 释放, 访问):

比如我要发送 1024 字节的数据, 如果用定长包, 假设定长包的长度 MAX_LENGTH 为 2048, 就会浪费 1024 个字节的空间, 也会造成不必要的流量浪费:

  • 数据结构定义:

//  定长缓冲区
struct max_buffer
{

    int     len;    
    char    data[MAX_LENGTH];
};
  • 数据结构大小:考虑对齐, 那么数据结构的大小 >= 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);
}
  • 访问:这段内存要分两部分使用;前部分 4 个字节 p->len, 作为包头(就是多出来的那部分),这个包头是用来描述紧接着包头后面的数据部分的长度,这里是 1024, 所以前四个字节赋值为 1024 (既然我们要构造不定长数据包,那么这个包到底有多长呢,因此,我们就必须通过一个变量来表明这个数据包的长度,这就是 len 的作用);而紧接其后的内存是真正的数据部分, 通过 p->data, 最后, 进行一个 memcpy() 内存拷贝, 把要发送的数据填入到这段内存当中

  • 释放:那么当使用完毕释放数据的空间的时候, 直接释放就可以了

// 销毁
free(mbuffer);
mbuffer = NULL;

2、小结:

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

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

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

如果你将上面的长度为 MAX_LENGTH 的定长数组换为指针, 每次使用时动态的开辟CURR_LENGTH 大小的空间, 那么就避免造成 MAX_LENGTH - CURR_LENGTH 空间的浪费, 只浪费了一个指针域的空间:

  • 数据包定义:

struct point_buffer
{

  int     len;
  char    *data;
};
  • 数据结构大小:考虑对齐, 那么数据结构的大小 >= 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);
  }
}

首先, 需为结构体分配一块内存空间;其次再为结构体中的成员变量分配内存空间。

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

  • 释放:相反, 释放时也是一样的:

/// 销毁
free(pbuffer->data);
free(pbuffer);
pbuffer = NULL;
  • 小结:

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

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

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

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

GNU C 的 0 长度数组, 也叫变长数组, 柔性数组就是这样一个扩展。对于 0 长数组的这个特点,很容易构造出变成结构体,如缓冲区,数据包等等:

  • 数据结构定义:

//  0长度数组
struct zero_buffer
{

  int     len;
  char    data[0];
};
  • 数据结构大小:这样的变长数组常用于网络通信中构造不定长数据包, 不会浪费空间浪费网络流量, 因为 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);
}
  • 释放空间:释放空间也是一样的, 一次释放即可

///  销毁
free(zbuffer);
zbuffer = NULL;
  • 总结:

// 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;
}



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

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

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

GNU Document中 变长数组的支持:

参考:

6.17 Arrays of Length Zero
C Struct Hack – Structure with variable length array

在 C90 之前, 并不支持 0 长度的数组, 0 长度数组是 GNU C 的一个扩展, 因此早期的编译器中是无法通过编译的;对于 GNU C 增加的扩展, GCC 提供了编译选项来明确的标识出他们:

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

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

  • -Werror, 它要求 GCC 将所有的警告当成错误进行处理

// 1.c
#include 
#include 
int main(void)
{
    char a[0];
  printf("%ld"sizeof(a));
  return EXIT_SUCCESS;
}

我们来编译:

# 显示所有警告
gcc 1.c -Wall
#none warning and error

# 对GNU C的扩展显示警告
gcc 1.c -Wall -pedantic
1.cIn function ‘main’:
1.c:7warningISO C forbids zero-size array ‘a

# 显示所有警告同时GNU C的扩展显示警告, 将警告用 error 显示
gcc 1.c -Werror -Wall -pedantic 
cc1warnings being treated as errors
1.cIn function ‘main’:
1.c:7errorISO C forbids zero-size array ‘a



0长度数组其实就是灵活地运用数组指向的是其后面连续的内存空间:

struct buffer
{

  int     len;
  char    data[0];
};

在早期没引入 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;
}

使用 -pedantic 编译后, 不出现警告, 说明这种语法是 C 标准的

gcc 2.c -pedantic -std=c99



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

GNU 手册还提供了另外两个结构体来说明,更容易看懂意思:

struct f1 
{

    int x;
  int y[];
} f1 = { 1, { 234 } };

struct f2
{

    struct f1 f1;
  int data[3];
} f2 = { { 1 }, { 567 } };

我把 f2 里面的 2,3,4 改成了 5,6,7 以示区分。如果你把数据打出来。即如下的信息:

f1.x = 1
f1.y[0] = 2
f1.y[1] = 3
f1.y[2] = 4

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

f2.f1.x = 1
f2.f1.y[0] = 5
f2.f1.y[1] = 6
f2.f1.y[2] = 7

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

main.c:37:9: error: flexible array member not at end of struct
                    int y[];
                            ^

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

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

struct f3
{

    int x;
  int *y;
};

在 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, { 234 } };

struct f2 
{

    struct f1 f1;
  int data[3];
} f2 = { { 1 }, { 567 } };

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;
}



0 长度数组的其他特征:

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;
}



//  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;
}



可以看到这两个程序虽然都存在访问异常, 但是段错误的位置却不同

我们将两个程序编译成汇编, 然后 diff 查看他们的汇编代码有何不同

gcc -S gdb_zero_length_array.c -o gdb_test.s
gcc -S gdb_pzero_length_array.c -o gdb_ptest
diff gdb_test.s gdb_ptest.s

1c1
<   .file   "gdb_zero_length_array.c"
---
>   .file   "gdb_pzero_length_array.c"
23c23
<   movl    $4, %esi
---
>   movl    $16, %esi
30c30
<   addq    $4, %rax
---
>   movq    8(%rax), %rax
36c36
<
addq    $4, %rax
---
>   movq    8(%rax), %rax  # printf("sizeof(struct str) = %d\n", sizeof(struct str));
23c23
<   movl    $4, %esi      #printf("sizeof(struct str) = %d\n", sizeof(struct str));
---
>   movl    $16, %esi     #printf("sizeof(struct str) = %d\n", sizeof(struct str));

从 64 位系统中, 汇编我们看出, 变长数组结构的大小为 4, 而指针形式的结构大小为 16:

f.a->s
30c30/36c36
<   addq    $4, %rax
---
>   movq    8(%rax), %rax

可以看到有:

  • 对于 char s[0] 来说, 汇编代码用了 addq 指令, addq $4, %rax

  • 对于 char *s 来说,汇编代码用了 movq 指令, movq 8(%rax), %rax

addq 对 %rax + sizeof(struct str), 即 str 结构的末尾即是 char s[0] 的地址, 这一步只是拿到了其地址, 而 movq 则是把地址里的内容放进去, 因此有时也被翻译为 leap 指令, 参见下一例子

从这里可以看到, 访问成员数组名其实得到的是数组的相对地址, 而访问成员指针其实是相对地址里的内容(这和访问其它非指针或数组的变量是一样的):

  • 访问相对地址,程序不会 crash,但是,访问一个非法的地址中的内容,程序就会 crash

// 4-1.c
#include 
#include 

int main(void)
{
    char *a;
  printf("%p\n", a);
  return EXIT_SUCCESS;
}


4-2.c
#include 
#include 

int main(void)
{
    char a[0];
  printf("%p\n", a);    return EXIT_SUCCESS;
}


$ diff 4-1.4-2.s
1c1
<       .file   "4-1.c"
---
>       .file   "4-2.c"
13c13
<       subl    $16, %esp
---
>       subl    $32, %esp
15c15
<       leal    16(%esp), %eax
---
>       movl    28(%esp), %eax
  • 对于 char a[0] 来说, 汇编代码用了 leal 指令, leal 16(%esp), %eax

  • 对于 char *a 来说,汇编代码用了 movl 指令, movl 28(%esp), %eax

2、地址优化:

// 5-1.c
#include 
#include 

int main(void)
{
    char a[0];
  printf("%p\n", a);

  char b[0];
  printf("%p\n", b);

  return EXIT_SUCCESS;
}


img

由于 0 长度数组是 GNU C 的扩展, 不被标准库任可, 那么一些巧妙编写的诡异代码, 其执行结果就是依赖于编译器和优化策略的实现的.

比如上面的代码, a 和 b 的地址就会被编译器优化到一处, 因为 a[0] 和 b[0] 对于程序来说是无法使用的, 这让我们想到了什么?

编译器对于相同字符串常量, 往往地址也是优化到一处, 减少空间占用:

//  5-2.c
#include 
#include 

int main(void)
{
    const char *a = "Hello";
  printf("%p\n", a);

  const char *b = "Hello";
  printf("%p\n", b);

  const char c[] = "Hello";
  printf("%p\n", c);

  return EXIT_SUCCESS;
}




版权声明:本文来源网络,免费传达知识,版权归原作者所有。如涉及作品版权问题,请联系我进行删除。


‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧  END  ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧

关注我的微信公众号,回复“星球”加入知识星球,有问必答。



点击“阅读原文”查看知识星球详情,欢迎点分享、收藏、点赞、在看。

李肖遥 公众号“技术让梦想更伟大”,作者:李肖遥,专注嵌入式,只推荐适合你的博文,干货,技术心得,与君共勉。
评论
  • 流量传感器是实现对燃气、废气、生活用水、污水、冷却液、石油等各种流体流量精准计量的关键手段。但随着工业自动化、数字化、智能化与低碳化进程的不断加速,采用传统机械式检测方式的流量传感器已不能满足当代流体计量行业对于测量精度、测量范围、使用寿命与维护成本等方面的精细需求。流量传感器的应用场景(部分)超声波流量传感器,是一种利用超声波技术测量流体流量的新型传感器,其主要通过发射超声波信号并接收反射回来的信号,根据超声波在流体中传播的时间、幅度或相位变化等参数,间接计算流体的流量,具有非侵入式测量、高精
    华普微HOPERF 2025-01-13 14:18 482浏览
  • PNT、GNSS、GPS均是卫星定位和导航相关领域中的常见缩写词,他们经常会被用到,且在很多情况下会被等同使用或替换使用。我们会把定位导航功能测试叫做PNT性能测试,也会叫做GNSS性能测试。我们会把定位导航终端叫做GNSS模块,也会叫做GPS模块。但是实际上他们之间是有一些重要的区别。伴随着技术发展与越发深入,我们有必要对这三个词汇做以清晰的区分。一、什么是GPS?GPS是Global Positioning System(全球定位系统)的缩写,它是美国建立的全球卫星定位导航系统,是GNSS概
    德思特测试测量 2025-01-13 15:42 492浏览
  • 根据Global Info Research(环洋市场咨询)项目团队最新调研,预计2030年全球无人机电池和电源产值达到2834百万美元,2024-2030年期间年复合增长率CAGR为10.1%。 无人机电池是为无人机提供动力并使其飞行的关键。无人机使用的电池类型因无人机的大小和型号而异。一些常见的无人机电池类型包括锂聚合物(LiPo)电池、锂离子电池和镍氢(NiMH)电池。锂聚合物电池是最常用的无人机电池类型,因为其能量密度高、设计轻巧。这些电池以输出功率大、飞行时间长而著称。不过,它们需要
    GIRtina 2025-01-13 10:49 190浏览
  • 数字隔离芯片是现代电气工程师在进行电路设计时所必须考虑的一种电子元件,主要用于保护低压控制电路中敏感电子设备的稳定运行与操作人员的人身安全。其不仅能隔离两个或多个高低压回路之间的电气联系,还能防止漏电流、共模噪声与浪涌等干扰信号的传播,有效增强电路间信号传输的抗干扰能力,同时提升电子系统的电磁兼容性与通信稳定性。容耦隔离芯片的典型应用原理图值得一提的是,在电子电路中引入隔离措施会带来传输延迟、功耗增加、成本增加与尺寸增加等问题,而数字隔离芯片的目标就是尽可能消除这些不利影响,同时满足安全法规的要
    华普微HOPERF 2025-01-15 09:48 83浏览
  • 随着通信技术的迅速发展,现代通信设备需要更高效、可靠且紧凑的解决方案来应对日益复杂的系统。中国自主研发和制造的国产接口芯片,正逐渐成为通信设备(从5G基站到工业通信模块)中的重要基石。这些芯片凭借卓越性能、成本效益及灵活性,满足了现代通信基础设施的多样化需求。 1. 接口芯片在通信设备中的关键作用接口芯片作为数据交互的桥梁,是通信设备中不可或缺的核心组件。它们在设备内的各种子系统之间实现无缝数据传输,支持高速数据交换、协议转换和信号调节等功能。无论是5G基站中的数据处理,还是物联网网关
    克里雅半导体科技 2025-01-10 16:20 444浏览
  • 新年伊始,又到了对去年做总结,对今年做展望的时刻 不知道你在2024年初立的Flag都实现了吗? 2025年对自己又有什么新的期待呢? 2024年注定是不平凡的一年, 一年里我测评了50余块开发板, 写出了很多科普文章, 从一个小小的工作室成长为科工公司。 展望2025年, 中国香河英茂科工, 会继续深耕于,具身机器人、飞行器、物联网等方面的研发, 我觉得,要向未来学习未来, 未来是什么? 是掌握在孩子们生活中的发现,和精历, 把最好的技术带给孩子,
    丙丁先生 2025-01-11 11:35 457浏览
  • 随着数字化的不断推进,LED显示屏行业对4K、8K等超高清画质的需求日益提升。与此同时,Mini及Micro LED技术的日益成熟,推动了间距小于1.2 Pitch的Mini、Micro LED显示屏的快速发展。这类显示屏不仅画质卓越,而且尺寸适中,通常在110至1000英寸之间,非常适合应用于电影院、监控中心、大型会议、以及电影拍摄等多种室内场景。鉴于室内LED显示屏与用户距离较近,因此对于噪音控制、体积小型化、冗余备份能力及电气安全性的要求尤为严格。为满足这一市场需求,开关电源技术推出了专为
    晶台光耦 2025-01-13 10:42 498浏览
  •   在信号处理过程中,由于信号的时域截断会导致频谱扩展泄露现象。那么导致频谱泄露发生的根本原因是什么?又该采取什么样的改善方法。本文以ADC性能指标的测试场景为例,探讨了对ADC的输出结果进行非周期截断所带来的影响及问题总结。 两个点   为了更好的分析或处理信号,实际应用时需要从频域而非时域的角度观察原信号。但物理意义上只能直接获取信号的时域信息,为了得到信号的频域信息需要利用傅里叶变换这个工具计算出原信号的频谱函数。但对于计算机来说实现这种计算需要面对两个问题: 1.
    TIAN301 2025-01-14 14:15 108浏览
  • 01. 什么是过程能力分析?过程能力研究利用生产过程中初始一批产品的数据,预测制造过程是否能够稳定地生产符合规格的产品。可以把它想象成一种预测。通过历史数据的分析,推断未来是否可以依赖该工艺持续生产高质量产品。客户可能会要求将过程能力研究作为生产件批准程序 (PPAP) 的一部分。这是为了确保制造过程能够持续稳定地生产合格的产品。02. 基本概念在定义制造过程时,目标是确保生产的零件符合上下规格限 (USL 和 LSL)。过程能力衡量制造过程能多大程度上稳定地生产符合规格的产品。核心概念很简单:
    优思学院 2025-01-12 15:43 523浏览
  • ARMv8-A是ARM公司为满足新需求而重新设计的一个架构,是近20年来ARM架构变动最大的一次。以下是对ARMv8-A的详细介绍: 1. 背景介绍    ARM公司最初并未涉足PC市场,其产品主要针对功耗敏感的移动设备。     随着技术的发展和市场需求的变化,ARM开始扩展到企业设备、服务器等领域,这要求其架构能够支持更大的内存和更复杂的计算任务。 2. 架构特点    ARMv8-A引入了Execution State(执行状
    丙丁先生 2025-01-12 10:30 466浏览
  • 食物浪费已成为全球亟待解决的严峻挑战,并对环境和经济造成了重大影响。最新统计数据显示,全球高达三分之一的粮食在生产过程中损失或被无谓浪费,这不仅导致了资源消耗,还加剧了温室气体排放,并带来了巨大经济损失。全球领先的光学解决方案供应商艾迈斯欧司朗(SIX:AMS)近日宣布,艾迈斯欧司朗基于AS7341多光谱传感器开发的创新应用来解决食物浪费这一全球性难题。其多光谱传感解决方案为农业与食品行业带来深远变革,该技术通过精确判定最佳收获时机,提升质量控制水平,并在整个供应链中有效减少浪费。 在2024
    艾迈斯欧司朗 2025-01-14 18:45 66浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦