【进阶C语言】编译与链接、预处理符号详解
目录
一、翻译环境
编译
1.预编译(预处理)
2.编译
3.汇编
链接
二、运行环境
三、预处理符号详解
1.预定义符号
2.#define
3.#undef
4..命令行定义
5.条件编译
6.头文件包含
代码是怎么变成可执行程序的?
一、翻译环境
翻译环境是将.c文件翻译成.exe文件,而执行环境是将.exe文件执行成代码。翻译环境又分为编译和链接两部分。
我们假设当前撰写的文件为test.c文件
编译
编译整个过程完成的事情:生成可执行程序,依赖编译器,如VS2022
1.预编译(预处理)
预处理阶段也成为文本操作,也就是处理完成之后我们还可以看得懂的。
【这个步骤做的事情】
(1)test.c文件会被处理成test.i文件。
(2)注释会被替换成一个空格,可以理解成被删除了。
(3)头文件包含的文件会完整的展开到文件里面(#include<>包含的文件会消失)
(4)#define符号的替换
所有的预处理指令都会在预编译阶段被处理掉
2.编译
生成.s文件
把c语言代码生成汇编代码
【编译步骤会完成的事情】
(1)test.i文件会变成test.s文件(也就是生成test.s文件)
(2)把C语言代码翻译成汇编代码(肉眼看不懂)
生成汇编代码需要做的事情:
1.词法分析 2.语法分析 3.语义分析 4.符号汇总
3.汇编
把汇编代码生成了二进制的指令,生成.o的文件(目标文件)
生成符号表
【汇编操作会完成的事情】
(在linux环境下生成的目标文件是.o文件,在windows环境下生成的是.obj文件)
(1)test.s文件会变成test.o文件(也就是产生test.o文件--目标文件)
(2)把汇编代码翻译成了二进制的指令,也就是生成的test.o文件
(3)生成符号表(在链接的步骤产生作用)
一般是汇总全局范围内可以看到的符号,例如:main函数等,像局部变量这些不会汇总。
链接
【在链接过程会完成的事情】
test.o--->test
(1)链接目标文件(test.o文件)和链接库生成可执行程序(二进制的程序)
(2)合并段表
把目标文件里面相同段落的数据进行合并
(3)符号表的合成和重定位(在汇编阶段生成的符号表)
把每个目标文件里面的符号汇总在一起(并附带相关的地址)
二、运行环境
运行环境也叫执行环境,用来实际执行代码。
在运行环境会完成的步骤:
1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
2. 程序的执行便开始。接着便调用main函数。
3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
4. 终止程序。正常终止main函数;也有可能是意外终止。
三、预处理符号详解
前言:预处理指令就是在翻译环境-->编译-->预编译(预处理),这一步完成的。
1.预定义符号
(1)认识
预定义符号是C语言内置的一些符号,有以下符号:
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
符号的输入细节:前后分别是两个下划线_ _,连起来:__
(2)怎么使用
看一段代码:
#include<stdio.h>
int main()
{printf("%s\n", __FILE__);printf("%d\n", __LINE__);printf("%s\n", __DATE__);printf("%s\n", __TIME__);printf("%s\n", __FUNCTION__);//文件正在编译哪个函数return 0;
}
看一下运行结果:
解析:
(3)最后的符号
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
说明当前的编译器为遵循ANSI C
2.#define
前言:该预处理指令的种类繁多
(1)#define定义标识符
【语法结构】
#define name stuff
标识符的内容是多组多样的,也就是将被替换的内容。
【使用例子1】定义标识符常量
#include<stdio.h>
#define MAX 100
#define M 3+5
int main()
{printf("%d\n",MAX);printf("%d\n",M);return 0;
}
MAX的值就是100。MAX通通会被替换成100
M会在预处理阶段被替换成3+5,不会计算成8
【使用例子2】定义字符串
#include<stdio.h>
#define STR "abcde"
int main()
{printf(STR);return 0;
}
【使用例子3】定义类型
#include<stdio.h>
#define TNT int
int main()
{TNT a = 520;printf("%d\n",a);return 0;
}
【使用例子4】可以替换成很多其他例子
#define MAX 1000
#define reg register //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;) //用更形象的符号来替换一种实现
#define CASE break;case //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \date:%s\ttime:%s\n" ,\__FILE__,__LINE__ , \__DATE__,__TIME__ )
注意:#define定义后面最后不要加分号(;)
(2)#define定义宏
【定义】
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。
#define name( parament-list ) stuff
//其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。
图解:
注意事项1:参数列表的左括号必须与name紧邻。
如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
【使用例子1】宏的使用
#include<stdio.h>
#define ADD(x,y) (x+y)
int main()
{int a = 3;int b = 5;int c = ADD(a,b);printf("%d\n",c);return 0;
}
替换过程:
【使用注意事项】括号的添加
#include<stdio.h>
#define ADD(x,y) x+y
int main()
{int a = 3;int b = 5;int c = 5*ADD(a,b);printf("%d\n",c);return 0;
}
我们会以为结果是40,其实不是
ADD会被替换成3+5,然后5*3+5才是最终处理的结果。
所以宏体需要添加括号。
做法:
#include<stdio.h>
#define ADD(x,y) ((x)+(y))
int main()
{int a = 3;int b = 5;int c = 5*ADD(a,b);printf("%d\n",c);return 0;
}
(3)#define替换规则
1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:
1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
(4)#和##
【#的作用】把一个宏参数变成对应的字符串。
我们先看一段代码:
#include<stdio.h>
int main()
{int a = 10;printf("the value of a is %d\n", a);int b = 10;printf("the value of a is %d\n", b);float c = 3.14;printf("the value of a is %f\n", c);return 0;
}
每一个printf中都有重复的内容,是否可以将其包装起来呢?因为函数无法传类型,所以我们可以用宏
#include<stdio.h>
#define PRINT(n,format) printf("the value of ""n"" is "format"\n", n)
int main()
{int a = 10;PRINT(a, "%d");int b = 10;PRINT(b, "%d");float c = 3.14;PRINT(c, "%lf");return 0;
}
宏替换后:
【正确做法】需要用到#
#include<stdio.h>
#define PRINT(n,format) printf("the value of "#n" is "format"\n", n)
int main()
{int a = 10;PRINT(a, "%d");int b = 10;PRINT(b, "%d");float c = 3.14;PRINT(c, "%lf");return 0;
}
这样就可以把宏参数n替换成对应的字符串。比如a传给n,n是一个宏参数,替换成对应的宏参数就是a。
【##的作用】##可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符。
【例子】
#include<stdio.h>
#define CAT(n,v) n##v
int main()
{int value10 = 666;printf("%d\n",CAT(value,10));return 0;
}
输出结果:666
##会把左右两个符号连接成一个符号,n##v就变成nv,于是value,10就变成了value10,就是666.
注意:这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。
(5)带副作用的宏参数
什么是副作用,例如++或者--操作赋,如a++之后a的值发生了改变,这就是副作用,宏参数使用这种操作符也会有这种后果。
x+1;//不带副作用
x++;//带有副作用
【例子】
#include<stdio.h>
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
int main()
{int x = 5;int y = 8;int z = MAX(x++, y++);printf("x=%d y=%d z=%d\n", x, y, z);//输出的结果是什么?return 0;
}
这就是宏参数的副作用
(6)宏和函数对比
【宏的优点】
1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。
所以宏比函数在程序的规模和速度方面更胜一筹。
2. 更为重要的是函数的参数必须声明为特定的类型。
所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。
宏的参数可以是类型:
#include<stdio.h>
#define MALLOC(num,type) (type*)malloc(num*sizeof(type))
int main()
{int* p = MALLOC(10,int);if (p == NULL){return;}return 0;
}
【宏的缺点】
1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
2. 宏是没法调试的。3. 宏由于类型无关,也就不够严谨。
4. 宏可能会带来运算符优先级的问题,导致程容易出现错。
【宏与函数的对比】
属性 | #define宏 | 函数 |
---|---|---|
代码长度 | 每次使用时,宏代码都会被插入到程序中。除了非常 小的宏之外,程序的长度会大幅度增长 | 函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码 |
执行速度 | 更快 | 存在函数的调用和返回的额外开 销,所以相对慢一些 |
操作符优先级 | 宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括 号。 | 函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预 测。 |
带有副作用的参数 | 参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果。 | 函数参数只在传参的时候求值一次,结果更容易控制。 |
参数类型 | 宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型 | 函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是不同的。 |
调试 | 宏是不方便调试的 | 函数是可以逐语句调试的 |
递归 | 宏是不能递归的 | 函数是可以递归的 |
(7)命名约定
把宏名全部大写
函数名不要全部大写
3.#undef
(1)定义
这条指令用于移除一个宏定义。
(2)
#include<stdio.h>
#define M 100
int main()
{printf("%d\n",M);#undef M//移除#define M 520//重新赋值printf("%d\n",M);return 0;
}
4..命令行定义
(1)定义
编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。
就如Java在控制台操作一样
(2)作用
当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候,这个特性有点用处。(假
定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大写,我们需要一个数组能够大写。)
当前VS无法验证
5.条件编译
(1)定义
可以选择性选择某一条代码进行编译
如:调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。
(2)第一种条件编译指令
【形式】
#if 常量表达式
//...
#endif
【代码】
#include<stdio.h>
int main()
{
#if 1printf("我喜欢你\n");
#endif#if 0printf("我不喜欢你\n");
#endifreturn 0;
}
运行起来看结果:
(3)第二种条件编译指令
【形式】这种用于多分支的条件编译,如if…else if…else语句类似
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
无论什么情况,最多也只会选择一块代码进行编译。
【代码】
#include<stdio.h>
int main()
{
#if 1printf("我喜欢你\n");
#elif 1printf("我不喜欢你\n");
#elif 1==100printf("爱你一生一世\n");
#elseprintf("不爱你\n");
#endifreturn 0;
}
运行结果:
(4)判断是否被定义
【形式1】
#if defined(symbol)//语句
#endif
#include<stdio.h>
#define M 200
int main()
{
#if defined(M)printf("如果M被定义则会打印这句话\n");
#endif
#if defined(N)printf("如果N被定义则会打印这句话\n");
#endifreturn 0;
}
它只会判断这个符号是否存在,不会判断其值的真假
【形式2】
#ifdef 符号//语句
#endif
#include<stdio.h>
#define M 200
int main()
{
#ifdef Mprintf("如果M被定义则会打印这句话\n");
#endif
#ifdef Nprintf("如果N被定义则会打印这句话\n");
#endifreturn 0;
}
【形式3】
#if !defined(symbol)//语句
#ifndef symbol
include<stdio.h>
#define M 200
int main()
{
#if !defined(M)printf("如果M没有被定义则会打印这句话\n");
#endif
#if !defined(N)printf("如果N没有被定义则会打印这句话\n");
#endifreturn 0;
}
(5)嵌套指令
【形式】
#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif
#endif
6.头文件包含
(1)两种头文件包含的形式
1.包含本地的文件(自己的.h文件)
#include"xxxxxx.h"
2.包含标准库的头文件
#include<xxxx.h>
【本地文件包含】#include"xxxxxx.h"
查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。
如果找不到就提示编译错误。
【库文件包含】#include<xxxx.h>
查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。
写的时候需要区分不同种类的头文件
(2)嵌套头文件
嵌套使用头文件会增加程序的负担,所以每个头文件只需要被包含一次即可
【方法1】使用条件编译
每个头文件的开头写:
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif //__TEST_H__
【方法2】在头文件前加上这一句话
#pragma once
相关文章:

【进阶C语言】编译与链接、预处理符号详解
目录 一、翻译环境 编译 1.预编译(预处理) 2.编译 3.汇编 链接 二、运行环境 三、预处理符号详解 1.预定义符号 2.#define 3.#undef 4..命令行定义 5.条件编译 6.头文件包含 代码是怎么变成可执行程序的? 一、翻译环境 翻译环境…...
spring.profiles生效顺序
服务在不同环境启动,需要的运行参数可能会有差异,不同启动环境也可能公用同一份运行参,为了方便对这些不同环境相同和差异参数进行管理,springboot提供了文件配置化形式对这些参数进行管理,对于不同环境的差异化参数使…...

【经典PageRank 】02/2 算法和线性代数
系列前文:【经典 PageRank 】01/2 PageRank的基本原理-CSDN博客 一、说明 并非所有连接都同样重要! 该算法由 Sergey 和 Lawrence 开发,用于在 Google 搜索中对网页进行排名。基本原则是重要或值得信赖的网页更有可能链接到其他重要网页。例…...
【微客云】91优惠话费充值API接口开发功能介绍
话费充值接口文档 接口版本:1.0 ―、引言 文档概述 本文档提供话费充值接口规范说明,提供一整套的完整的接入示例(http 接口)供商户参 考,可以帮助商户开发人员快速完成接口开发与联调,实现与话费充值系统的交易互联。 公司官网…...

Kubernetes - 一键安装部署 K8S(附:Kubernetes Dashboard)
问题描述 不知道大伙是如何安装 K8s,特别还是集群的时候,我上一次安装搭建的时候,那个恶心到我了,真的是一步一个脚印走完整个搭建流程,爬了不少坑。 于是,才有了今天的文章,到底有没有可以一…...
Camera2开发基础知识篇——手机影像参数
1. 2、对焦 对焦指相机将图像清晰聚焦的过程。自动对焦功能可自动调整焦点,确保主体清晰锐利。手动对焦功能允许用户手动选择焦点。 3、焦距 简单理解就是指镜头的视角和放大倍数。实际到物理设备,焦距就是从镜片光学中心到底片、CCD或CMOS等成像平面…...

Unity之ShaderGraph如何实现无贴图水球效果
前言 我们今天来实现一个无贴图水球效果,如下图所示: 主要节点 UVSplit:可以获得UV在RGB三个颜色分别的分量 Remap:重映射节点 基于输入 In 值在输入In Min Max的 x 和 y 分量之间的线性插值,返回输入Out Min Max…...

【C语言】指针错题(类型分析)
题目: #include <stdio.h> int main () {int*p NULL;int arr[10] {0}; return 0; } 选项: A、p arr ; B、 int (* ptr )[10]& arr ; C、 p & arr [ 0 ]; D、 p & arr ; 解析: 1、 p 是一个指针变量,指…...

prosemirror 学习记录(二)创建 apple 节点
apple type 向 schema 中添加 apple type const nodes {apple: {inline: true,attrs: {name: { default: "unknown" },},group: "inline",draggable: true,parseDOM: [{tag: "span[custom-node-typeapple]",getAttrs(dom) {return {name: dom…...
自然语言处理---迁移学习
fasttext介绍 作为NLP工程领域常用的工具包,fasttext有两大作用:进行文本分类、训练词向量。在保持较高精度的情况下,快速的进行训练和预测是fasttext的最大优势。fasttext优势的原因: fasttext工具包中内含的fasttext模型具有十分简单的网络…...
node 第十天 原生node封装一个简易的服务器
原生node封装一个简易的服务器, 把前面几天的知识揉和起来做一个服务器基础实现, 首页访问, 静态资源服务器, 特定接口封装, 404app.js 服务器入口文件 app.js node app.js即可启动服务器 const { start } require(./modules/server); start();require_modules.js 整合模块导…...
php实战案例记录(25)intval函数的用法
在PHP中,intval()函数用于将一个字符串转换为整数。它的语法如下: intval(string $value, int $base 10): int参数说明: $value:要转换的字符串。$base(可选):进制数,默认为10。如…...

laravel框架介绍(二) composer命令下载laravel报错
1.composer命令下载laravel报如下错 : curl error 18 while downloading https://repo.packagist.org/p2/symfony/uid.j son: transfer closed with 3808 bytes remaining to read,具体为 解决方案:执行以下命令切换镜像 >composer con…...

代码签名证书到期了怎么续费?
我们都知道代码签名证书最长期限可以申请3年,但有的首次申请也会申请1年,这种情况下证书到期了就意味着要重新办理,同样的实名验证步骤还需要再走一遍,尤其目前无论是哪种类型的代码签名证书都会有物理硬件,即使交钱实…...
JAVA 同城服务预约家政小程序开发的优势和运营
随着社会节奏的加快,人们对家庭清洁和维护的需求日益增长。为了满足这一需求,JAVA同城服务预约家政小程序应运而生。本文将详细介绍该小程序开发的优势及运营策略,帮助读者更好地了解其价值和潜力。 一、开发优势 方便快捷:用户…...

基于粒子群算法的无人机航迹规划-附代码
基于粒子群算法的无人机航迹规划 文章目录 基于粒子群算法的无人机航迹规划1.粒子群搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要:本文主要介绍利用粒子群算法来优化无人机航迹规划。 1.粒子群…...

前端使用qrcodejs2插件实现根据网址生成二维码
实现效果: 实现方法: 1.安装插件 npm install --save qrcodejs2 2.可以全局引入,也可以只在使用的vue文件中引入 import QRCode from qrcodejs2; 3.在vue文件的template中设置放置二维码的div <div id"qrcode"></di…...

A股风格因子看板 (2023.10 第11期)
该因子看板跟踪A股风格因子,该因子主要解释沪深两市的市场收益、刻画市场风格趋势的系列风格因子,用以分析市场风格切换、组合风格暴露等。 今日为该因子跟踪第11期,指数组合数据截止日2023-09-30,要点如下 近1年A股风格因子检验统…...
anaconda安装python 3.11
最近需要测试gpt researcher项目,gpt researcher项目的环境是3.11,于是用anaconda创建一个虚拟环境,结果报错了: UnsatisfiableError: The following specifications were found to be incompatible with each other:Package xz c…...
问题:EventSource 收不到流数据及 EventSource 的 onmessage 方法为null
文章目录 问题分析问题 在开发时,有用到 EventSource,但是在 new EventSource 的时候,打印 new EventSource 如下: onmessage : null, onerror : null, onopen: f(event)前端...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...

cf2117E
原题链接:https://codeforces.com/contest/2117/problem/E 题目背景: 给定两个数组a,b,可以执行多次以下操作:选择 i (1 < i < n - 1),并设置 或,也可以在执行上述操作前执行一次删除任意 和 。求…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...

在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)
考察一般的三次多项式,以r为参数: p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]; 此多项式的根为: 尽管看起来这个多项式是特殊的,其实一般的三次多项式都是可以通过线性变换化为这个形式…...
Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换
目录 关键点 技术实现1 技术实现2 摘要: 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式(自动驾驶、人工驾驶、远程驾驶、主动安全),并通过实时消息推送更新车…...
Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成
一个面向 Java 开发者的 Sring-Ai 示例工程项目,该项目是一个 Spring AI 快速入门的样例工程项目,旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计,每个模块都专注于特定的功能领域,便于学习和…...

MyBatis中关于缓存的理解
MyBatis缓存 MyBatis系统当中默认定义两级缓存:一级缓存、二级缓存 默认情况下,只有一级缓存开启(sqlSession级别的缓存)二级缓存需要手动开启配置,需要局域namespace级别的缓存 一级缓存(本地缓存&#…...

Kubernetes 节点自动伸缩(Cluster Autoscaler)原理与实践
在 Kubernetes 集群中,如何在保障应用高可用的同时有效地管理资源,一直是运维人员和开发者关注的重点。随着微服务架构的普及,集群内各个服务的负载波动日趋明显,传统的手动扩缩容方式已无法满足实时性和弹性需求。 Cluster Auto…...

海云安高敏捷信创白盒SCAP入选《中国网络安全细分领域产品名录》
近日,嘶吼安全产业研究院发布《中国网络安全细分领域产品名录》,海云安高敏捷信创白盒(SCAP)成功入选软件供应链安全领域产品名录。 在数字化转型加速的今天,网络安全已成为企业生存与发展的核心基石,为了解…...