你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

C语言——main函数

[复制链接]
gaosmile 发布时间:2020-12-8 17:54
main的返回值

main函数的返回值用于说明程序的退出状态。如果返回0,则代表程序正常退出。返回其它数字的含义则由系统决定。通常,返回非零代表程序异常退出。

void main()

有一些书上的,都使用了void main( ) ,其实这是错误的。C/C++ 中从来没有定义过void main( ) 。

C++ 之父 Bjarne Stroustrup 在他的主页上的 FAQ 中明确地写着 “The definition void main( ) { /* … */ } is not and never has been C++, nor has it even been C.” 这可能是因为 在 C 和 C++ 中,不接收任何参数也不返回任何信息的函数原型为“void foo(void);”。

可能正是因为这个,所以很多人都误认为如果不需要程序返回值时可以把main函数定义成void main(void) 。然而这是错误的!main 函数的返回值应该定义为 int 类型,C 和 C++ 标准中都是这样规定的。

虽然在一些编译器中,void main() 可以通过编译,但并非所有编译器都支持 void main() ,因为标准中从来没有定义过 void main 。

g++3.2 中如果 main 函数的返回值不是 int 类型,就根本通不过编译。而 gcc3.2 则会发出警告。所以,为了程序拥有很好的可移植性,一定要用 int main ()。测试如下:

#include <stdio.h>; s9 C2 a9 I/ ]& r' D3 M
4 g* X9 i6 e! o$ h
void main()
! Q# o1 W6 s; p5 i+ _{% n7 E- r( Z2 E3 C' T7 O" [
    printf("Hello world\n");
! Y3 a) M% U8 J" O1 c7 k    return;0 \% {& T+ r7 v/ v
}
& i2 \$ ]3 P$ o# n7 c2 e* F# h

运行结果:g++ test.c

微信图片_20201208174539.png
main()

那既然main函数只有一种返回值类型,那么是不是可以不写?规定:不明确标明返回值的,默认返回值为int,也就是说 main()等同于int main(),而不是等同于void main()。

在C99中,标准要求编译器至少给 main() 这种用法来个警告,而在c89中这种写法是被允许的。但为了程序的规范性和可读性,还是应该明确的指出返回值的类型。测试代码:

#include <stdio.h>
% T( |1 I* B  c- l' H9 P: l. o- U* _: z+ K2 A5 d* N
main()" A1 T) {& c; g
{
- Y8 l2 `4 D8 P1 m" P    printf("Hello world\n");
3 S: v7 b! \7 k/ I( {0 F2 b    return 0;* K6 G+ ]1 a1 m6 C; t0 i
}
. f- i: |) \# t7 h) Z

运行结果:

微信图片_20201208174557.png
C和C++的标准

在 C99 标准中,只有以下两种定义方式是正确的:

int main( void )
9 C  N( n' z$ a# ^5 S. l( Kint main( int argc, char *argv[] )
( t5 b8 f& W1 F6 F! O. z' G) {

若不需要从命令行中获取参数,就使用int main(void) ;否则的话,就用int main( int argc, char *argv[] )。当然参数的传递还可以有其他的方式,在下一节中,会单独来讲。

main 函数的返回值类型必须是 int ,这样返回值才能传递给程序的调用者(如操作系统),等同于 exit(0),来判断函数的执行结果。

C++89中定义了如下两种 main 函数的定义方式:

int main( ) 0 u4 Z7 a3 ?* G- b; e1 E
int main( int argc, char *argv[] )
3 J, H& a3 H) j  o3 n5 m" _

int main( ) 等同于 C99 中的 int main( void ) ;int main( int argc, char*argv[] ) 的用法也和C99 中定义的一样。同样,main函数的返回值类型也必须是int。

return 语句

如果 main 函数的最后没有写 return 语句的话,C99 和c++89都规定编译器要自动在生成的目标文件中加入return 0,表示程序正常退出。

不过,建议你最好在main函数的最后加上return语句,虽然没有这个必要,但这是一个好的习惯。在linux下我们可以使用shell命令:echo $? 查看函数的返回值。

#include <stdio.h>0 w) O1 B6 ^& V4 E. }( T

; h5 y) y7 \6 a1 R" v2 d# K4 ]' w5 Fint main(): F: `4 ]2 s5 L! j) E! ^# x, N
{
  z1 g# }/ _! }3 d' E( P9 y+ ^    printf("Hello world\n");/ M1 o* `3 O3 q, }! v3 |& F# x
}
$ i# }6 |$ m  \6 l; p$ H3 b4 k& B

运行结果:

微信图片_20201208174547.png

同时,需要说明的是return的返回值会进行 类型转换,比如:若return 1.2 ;会将其强制转换为1,即真正的返回值是1,同理,return ‘a’ ;的话,真正的返回值就是97,;但是若return “abc”;便会报警告,因为无法进行隐式类型转换。

测试main函数返回值的意义

前文说到,main函数如果返回0,则代表程序正常退出。通常,返回非零代表程序异常退出。在本文的最后,测试一下:  test.c:

#include <stdio.h>& N1 w4 q& d: p9 L
9 C$ b- U0 o# `7 A
int main()0 F6 g7 X( {! {
{+ H4 C2 ~4 N& w' d
    printf("c 语言\n");8 ^1 _" ^/ P. V" o
    return 11.1;
' w. v( i3 l. s" p* v4 ~1 K: A}! a* ]1 t0 Q6 X9 F/ c7 \

在终端执行如下:

➜  testSigpipe gitmaster) ✗ vim test.c
( w$ J! n7 A0 ?2 t' g# b➜  testSigpipe gitmaster) ✗ gcc test.c: q8 @4 W* ~! _
➜  testSigpipe gitmaster) ✗ ./a.out && echo "hello world"  #&&与运算,前面为真,才会执行后边的3 u: T3 Z0 U4 L2 A
c 语言
' U* [* j' ^$ H5 r8 c

可以看出,操作系统认为main函数执行失败,因为main函数的返回值是11

➜  testSigpipe gitmaster) ✗ ./a.out : e  |$ w' n+ X; J5 N
➜  testSigpipe gitmaster) ✗ echo $?
# ]9 j. U# ~: t! P1 \" h6 L+ D11
9 B) \8 x* e4 z  v( V5 A+ ~

若将main函数中返回值该为0的话:

➜  testSigpipe gitmaster) ✗ vim test.c
& a- W# e: D4 I4 G➜  testSigpipe gitmaster) ✗ gcc test.c 8 A; c5 S0 t' J" O6 a1 f7 Z9 g
➜  testSigpipe gitmaster) ✗ ./a.out && echo "hello world" #hello
; [2 X; j, t7 z: V$ x3 A' w: Vc 语言% z# U  m7 u/ q% C6 @
hello world
& p, ]8 M4 y: L: r3 \

可以看出,正如我们所期望的一样,main函数返回0,代表函数正常退出,执行成功;返回非0,代表函数出先异常,执行失败。

main函数传参

首先说明的是,可能有些人认为main函数是不可传入参数的,但是实际上这是错误的。main函数可以从命令行获取参数,从而提高代码的复用性。

函数原形

为main函数传参时,可选的main函数原形为:

int main(int argc , char* argv[],char* envp[]);
. s( Q6 L& F$ [- k

参数说明:

①、第一个参数argc表示的是传入参数的个数 。

②、第二个参数char* argv[],是字符串数组,用来存放指向的字符串参数的指针数组,每一个元素指向一个参数。各成员含义如下:

argv[0]:指向程序运行的全路径名。

argv[1]:指向执行程序名后的第一个字符串 ,表示真正传入的第一个参数。

argv[2]:指向执行程序名后的第二个字符串 ,表示传入的第二个参数。

…… argv[n]:指向执行程序名后的第n个字符串 ,表示传入的第n个参数。

规定:argv[argc]为NULL ,表示参数的结尾。

③、第三个参数char* envp[],也是一个字符串数组,主要是保存这用户环境中的变量字符串,以NULL结束。envp[]的每一个元素都包含ENVVAR=value形式的字符串,其中ENVVAR为环境变量,value为其对应的值。

envp一旦传入,它就只是单纯的字符串数组而已,不会随着程序动态设置发生改变。可以使用putenv函数实时修改环境变量,也能使用getenv实时查看环境变量,但是envp本身不会发生改变;平时使用到的比较少。

注意:main函数的参数char* argv[]和char* envp[]表示的是字符串数组,书写形式不止char* argv[]这一种,相应的argv[][]和 char** argv均可。

char* envp[]

写个小测试程序,测试main函数的第三个参数:

#include <stdio.h>9 K0 x! x0 w9 S% z, ]5 C
6 x; r/ U7 J% W5 w2 M! U( t1 ]
int main(int argc ,char* argv[] ,char* envp[])9 W* w. q# z: ^+ ^4 E
{
& a( C/ f' t% e8 e    int i = 0;7 @7 L2 A' E1 p* Q7 c) k* {- @

* G" I+ Y+ N7 F* z    while(envp[i++])( G6 `3 Q9 N* V% r
    {
& z  B  M# R' D        printf("%s\n", envp);. y% S0 i1 D& Y: b! y& }
    }' \( }$ X& q! A( z) W
6 D7 o# A3 R( j1 t+ k0 U
    return 0;
6 T8 V" R; _+ R' o( z}# n! w  W( n. l3 `% C; e/ k

运行结果:部分截图

微信图片_20201208174600.png

envp[] 获得的信息等同于Linux下env命令的结果。

常用版本

在使用main函数的带参版本的时,最常用的就是:**int main(int argc , char* argv[]);**变量名称argc和argv是常规的名称,当然也可以换成其他名称。

命令行执行的形式为:可执行文件名 参数1 参数2 … … 参数n。可执行文件名称和参数、参数之间均使用空格隔开。

示例程序#include <stdio.h>" L# t1 f; {9 Y" R* A& _$ Z( d4 |

% L/ C/ J' U: a( q* S$ U( h8 \int main(int argc, char* argv[])2 `+ Z- V3 {: F! i1 A4 c9 B
{
( e( Y$ t$ E& b! G+ {% A3 b
: J: ?, O6 K: S' V' c2 A4 [  F/ P    int i;# ?5 J2 e6 d) N% }+ h; s
    printf("Total %d arguments\n",argc);
; k) P9 d  h* V( z8 ~3 ]- {- h0 C4 P9 L( x- w* V( a
    for(i = 0; i < argc; i++)
3 H  X) `& F, C& m    {9 e6 G! @0 _+ i  L4 X
        printf("\nArgument argv[%d]  = %s \n",i, argv);
9 c0 W. C& \# O- `" |. c6 r4 J$ \9 `. q    }0 M2 ]  ?) u5 b; d. q
; ^, W; A( E; r; H5 C4 j1 k' R
    return 0;- P5 w7 g( e. w& e4 ~5 t
}
  @8 i5 Z- \% b9 g

运行结果:

➜  cpp_workspace gitmaster) ✗ vim testmain.c ' R! Z1 p8 i$ z: W( i# {/ B  d
➜  cpp_workspace gitmaster) ✗ gcc testmain.c 6 k4 T! l% X! G& I4 m. z
➜  cpp_workspace git:(master) ✗ ./a.out 1 2 3    #./a.out为程序名 1为第一个参数 , 2 为第二个参数, 3 为第三个参数+ a1 y6 b$ M2 p! B4 |, M! J
Total 4 arguments: J- E) L+ v) c  }
Argument argv[0]  = ./a.out , ?* q+ @9 H) a9 y/ f; s" N; n
Argument argv[1]  = 1
( s7 L! t, k+ ~9 [: \; v5 j2 G$ m" P# AArgument argv[2]  = 2 * @: n9 H3 |* {5 {5 \: H  R
Argument argv[3]  = 3 7 d1 B# K1 Z: \2 i1 ~8 o; J
Argument argv[4]  = (null)    #默认argv[argc]为null4 D) \/ _9 {. u* t: g, S
main的执行顺序

可能有的人会说,这还用说,main函数肯定是程序执行的第一个函数。那么,事实果然如此吗?相信在看了本节之后,会有不一样的认识。

为什么说main()是程序的入口

linux系统下程序的入口是”_start”,这个函数是linux系统库(Glibc)的一部分,当我们的程序和Glibc链接在一起形成最终的可执行文件的之后,这个函数就是程序执行初始化的入口函数。通过一个测试程序来说明:

#include <stdio.h>
9 _1 U) l' k) M1 }, K
' H& ~8 x3 |# ?8 oint main()9 z- ~0 {+ ^! W5 Z0 X
{
+ [$ [% a: e$ E2 b' I    printf("Hello world\n");5 B/ V2 T, Z" a# F5 ], }0 u7 r
    return 0;
" Y8 [" _" i1 R) N& X3 p}
+ h# r. c3 F5 c; ^

编译:

gcc testmain.c -nostdlib     # -nostdlib (不链接标准库)3 T  @+ X: r% `/ O

程序执行会引发错误:/usr/bin/ld: warning: cannot find entry symbol _start; 未找到这个符号

所以说:

  • 编译器缺省是找 __start 符号,而不是 main
  • __start 这个符号是程序的起始
  • main 是被标准库调用的一个符号8 d& [2 m3 q: C/ {6 a

那么,这个_start和main函数有什么关系呢?下面我们来进行进一步探究。

_start函数的实现该入口是由ld链接器默认的链接脚本指定的,当然用户也可以通过参数进行设定。_start由汇编代码实现。大致用如下伪代码表示:

void _start()
8 n1 N: Z) k3 N{
% ?; s7 f; D6 u7 M3 \3 ?  %ebp = 0;
$ K* R8 l$ l7 a7 n2 a  int argc = pop from stack
1 @9 U0 `0 K# W( F1 F+ e. g; B  char ** argv = top of stack;" h2 D6 N3 H9 @3 j4 F
  __libc_start_main(main, argc, argv, __libc_csu_init, __linc_csu_fini,7 z* I/ Z2 `9 ?# v
  edx, top of stack);
9 X- E- a3 J% ]& G2 ?8 b}9 c  u7 P. `1 i7 ^2 z* x+ g

对应的汇编代码如下:

_start:9 q3 u2 I! P' N. F
xor ebp, ebp //清空ebp: I4 {. R3 k! y+ j; a- ]- W" |8 h
pop esi //保存argc,esi = argc
; @* L. |8 T* `! q: x6 | mov esp, ecx //保存argv, ecx = argv! q3 d8 I; `- f- I0 l. n
$ y+ P' j# w) F% G* z2 i
push esp //参数7保存当前栈顶, S7 k' ~; b8 ?9 t  }
push edx //参数6; e( B+ D# ]+ |/ E8 Z2 Y+ A! h' S
push __libc_csu_fini//参数5
* F- D& a  o4 ], t# }& k push __libc_csu_init//参数4
* X+ p# a3 r8 l0 j push ecx //参数3
+ p6 s8 r- k! A4 t) o3 n' X push esi //参数2. j! f, D4 B2 X# z" g  b% ~
push main//参数1
. k5 Y0 R5 _9 h! T call _libc_start_main
" z* i. i$ l4 I* O$ j9 [+ [5 C* X$ \, e5 q
hlt
0 D) |1 n+ X! G8 R+ G" U

可以看出,在调用_start之前,装载器就会将用户的参数和环境变量压入栈中。

main函数运行之前的工作

从_start的实现可以看出,main函数执行之前还要做一系列的工作。主要就是初始化系统相关资源:

Some of the stuff that has to happen before main():$ A2 j) j) K$ p, _% I  E: |( w

6 ~4 s+ t. I( \set up initial stack pointer 1 p: a( O7 B/ o& u
: F6 F6 ]- h6 l- r. {0 V) u
initialize static and global data 8 k$ R- H4 ~3 g1 y$ ^1 w

( W2 X2 `/ j6 |1 f5 ezero out uninitialized data
( b3 t. W8 Y0 x- t, {3 v& ~* h* d8 `: D3 G. L5 o' _
run global constructors
' H- C- A1 U. r& R9 E; r
" A: |8 E0 N! X, E" A  Q: \1 J" \Some of this comes with the runtime library's crt0.o file or its __start() function. Some of it you need to do yourself.7 x& E% F8 h2 M2 U+ t5 m" {
+ _' f$ u4 H4 Y; U3 ?7 h
Crt0 is a synonym for the C runtime library.
# S3 ?  r6 _: ^0 k3 e0 t( x0 U

1.设置栈指针

2.初始化static静态和global全局变量,即data段的内容

3.将未初始化部分的赋初值:数值型short,int,long等为0,bool为FALSE,指针为NULL,等等,即.bss段的内容

4.运行全局构造器,类似c++中全局构造函数

5.将main函数的参数,argc,argv等传递给main函数,然后才真正运行main函数

main之前运行的代码

下面,我们就来说说在mian函数执行之前到底会运行哪些代码:(1)全局对象的构造函数会在main 函数之前执行。

(2)一些全局变量、对象和静态变量、对象的空间分配和赋初值就是在执行main函数之前,而main函数执行完后,还要去执行一些诸如释放空间、释放资源使用权等操作

(3)进程启动后,要执行一些初始化代码(如设置环境变量等),然后跳转到main执行。全局对象的构造也在main之前。

(4)通过关键字attribute,让一个函数在主函数之前运行,进行一些数据初始化、模块加载验证等。

示例代码

①、通过关键字attribute

#include <stdio.h>9 D' c1 i. x; d8 G4 V" ^) r6 _1 ?

$ K+ z/ Y+ \. Z__attribute__((constructor)) void before_main_to_run()
; z3 f# k" t& J. J{ + C; r0 ?3 ?& C4 X" H( O  X3 e
    printf("Hi~,i am called before the main function!\n");
& g7 y) h4 p5 g2 P    printf("%s\n",__FUNCTION__); ! {7 G) g, T; A
} 8 T: Q0 s1 ]- I. P0 y, q; F7 w) i
' U# K+ N# u4 O* X% S) L: b' Q
__attribute__((destructor)) void after_main_to_run()
0 D% \4 T3 V( i, \- U. p{ ) K8 G  X* I' i
    printf("%s\n",__FUNCTION__);
. D, u  N+ O  ]$ l% e- n& M6 R    printf("Hi~,i am called after the main function!\n");
2 @7 P: X' N4 ]* {}
1 |  l2 s6 ]+ ^) }0 L8 x# R
7 }8 ~* ]; j* [& L3 p" H- S. yint main( int argc, char ** argv ) $ v( x2 N; x! Z. E
{ + A# y& J3 T2 T) _" D8 r9 L
    printf("i am main function, and i can get my name(%s) by this way.\n",__FUNCTION__); 0 u" L# T0 H9 B% }: P* E
    return 0; / E, T$ O0 [0 X$ S9 J
}8 J: \# W/ c" B+ i

②、全局变量的初始化

#include <iostream>
. n9 v$ `3 a/ j7 T% ?1 ]: y& f4 \# r8 n
using namespace std;5 C) I! K6 \: v$ F% k

5 Q' b* G# c  z9 c' Binline int startup_1(). B4 |  z8 Z8 y& K/ ]
{9 v1 d. ?+ r8 [" [$ T) u
    cout<<"startup_1 run"<<endl;; Z6 b4 {- `3 ?$ k
    return 0;6 D6 D" ]& P* w4 a
}
( `* S9 }! Y4 w$ N2 [9 j- k  o" a9 f7 x( ~8 t
int static no_use_variable_startup_1 = startup_1();
  w* G. C8 f/ m( H1 L; ]! N
2 c; u9 i8 G, K. s3 L6 tint main(int argc, const char * argv[])
1 E, b% H- }! Y2 P. F- s# I5 O. w{! f' H! d, }9 r" H
    cout<<"this is main"<<endl;% V4 a, e9 _* \/ \3 D. x6 b
    return 0;
- J+ _& b, w* `( ]. G, s- E6 B}
6 d" H9 X; X* V( T% u$ u$ F4 o

至此,我们就聊完了main函数执行之前的事情,那么,你是否还以为main函数也是程序运行的最后一个函数呢?

结果当然不是,在main函数运行之后还有其他函数可以执行,main函数执行完毕之后,返回到入口函数,入口函数进行清理工作,包括全局变量析构、堆销毁、关闭I/O等,然后进行系统调用结束进程。

main函数之后执行的函数

1、全局对象的析构函数会在main函数之后执行; 2、用atexit注册的函数也会在main之后执行。

atexit函数

原形:

int atexit(void (*func)(void)); 9 r( r) p1 G! _" R8 t3 r' \' i2 X3 L/ @

atexit 函数可以“注册”一个函数,使这个函数将在main函数正常终止时被调用,当程序异常终止时,通过它注册的函数并不会被调用。

编译器必须至少允许程序员注册32个函数。如果注册成功,atexit 返回0,否则返回非零值,没有办法取消一个函数的注册。

在 exit 所执行的任何标准清理操作之前,被注册的函数按照与注册顺序相反的顺序被依次调用。每个被调用的函数不接受任何参数,并且返回类型是 void。被注册的函数不应该试图引用任何存储类别为 auto 或 register 的对象(例如通过指针),除非是它自己所定义的。

多次注册同一个函数将导致这个函数被多次调用。函数调用的最后的操作就是出栈过程。main()同样也是一个函数,在结束时,按出栈的顺序调用使用atexit函数注册的,所以说,函数atexit是注册的函数和函数入栈出栈一样,是先进后出的,先注册的后执行。通过atexit可以注册回调清理函数。可以在这些函数中加入一些清理工作,比如内存释放、关闭打开的文件、关闭socket描述符、释放锁等等。

#include<stdio.h>
1 ]0 a; S* m5 f& w4 \5 `#include<stdlib.h>
0 N' n9 p" ^3 n* Q
+ n  b* T+ M# ^* ~' {7 c/ fvoid fn0( void ), fn1( void ), fn2( void ), fn3( void ), fn4( void );
" I; e- ^( N6 A: |1 c* `, K
  A* c$ b, g) z! R8 pint main( void )
! N/ b5 R6 {4 s$ V( X
+ }- F! J% ~1 D  w8 F8 }{
; [5 m) a, Q, |. n; K, J9 e7 y  //注意使用atexit注册的函数的执行顺序:先注册的后执行3 P# D9 R9 y& q6 w5 A
    atexit( fn0 );  
/ z- W$ w) |, K* S. m    atexit( fn1 );  * {4 X# G& o+ L
    atexit( fn2 );  
* N/ q5 _) c7 b/ K, q6 p    atexit( fn3 );  
$ A: H3 p  ]$ K    atexit( fn4 );
" N1 O' W0 e- C8 i, s( R9 A! O* m
7 N8 l6 k5 e* X    printf( "This is executed first.\n" );
* y: Y" a* a  E* X1 b    printf("main will quit now!\n");
+ \7 J7 F3 D2 c2 Y  k: C) F" R0 d6 c( I& e
    return 0;  W( B) V, n' x' M7 ?5 C
" x5 r$ g# W6 Q) t3 ^/ f% P
}# I9 x- F% [# y2 M

  f: t) d7 Q9 E0 h: ovoid fn0()
: @( t" T! u% i0 P7 [( P{
6 a( @. P  \2 M3 Y    printf( "first register ,last call\n" );
& O/ x$ W, i+ e$ M6 Q" z& o1 O}
( d% b7 D( G" ~* Y  H. Y
3 v0 n5 N$ [; P4 b0 j/ [9 `  wvoid fn1(+ f- p. z% K$ [& N: c( ]) {
{
8 j& ]/ e2 r; C    printf( "next.\n" );
5 D2 D" {! Q7 P/ N' }4 H7 j) a" V  v}9 n: q; k+ a  v9 c
2 P! P2 a/ Z0 G, ?; v/ o) o
void fn2()
1 u) @. N/ v! f9 E$ C$ a0 \{6 r, W" u- z  j0 e' O  H* X& R
    printf( "executed " );
* c) i7 |/ L/ M* ?& I}
) n/ I, B/ i+ ?2 |5 F. w9 u7 G% x- h# p9 X1 n! f/ E7 f) r
void fn3()# u- O" w, ]0 `, Q
{
9 T; L4 \& H8 W9 B) ?2 |    printf( "is " );
/ T& T( O# j( @5 ]* T4 {}) l1 y% U' S3 a1 Q7 o! T& b  @

7 m" r# [: e+ Q& H% gvoid fn4()' Q' |" o6 C5 t/ o, M& @/ W
{
8 |6 A2 X" u8 O4 j# _    printf( "This " );
- y8 d4 x# x$ n: S}

% [1 s9 i" y  p& \4 s1 O+ w
收藏 评论0 发布时间:2020-12-8 17:54

举报

0个回答

所属标签

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版