纳磁生物面试总结:
面试总结:1.智能指针 在多线程使用中 安全的 吗?
答: 不安全;
本身是安全的 , 但是 对象 是 不安全的 ;
智能指针shared_ptr的线程安全、互斥锁_智能指针是线程安全的吗-CSDN博客
【 所有智能指针在多线程下引用计数也是安全的,也就是说智能指针在多线程下传递使用时引用计数是不会有线程安全问题的。
对于智能指针shared_ptr的引用计数本身是安全且无锁的,但对象的读写则不是
因为 shared_ptr 有两个数据成员,一个是指向的对象的指针,还有一个就是我们上面看到的引用计数管理对象,
当智能指针发生拷贝的时候,标准库的实现是先拷贝智能指针,再拷贝引用计数对象(拷贝引用计数对象的时候,会使use_count加一),
这两个操作并不是原子操作,隐患就出现在这里。两个线程中智能指针的引用计数同时++或--,这个操作不是原子的,假设引用计数原来是1,++了两次,可能还是2,这样引用计数就错乱了,违背了原子性。
】
【3. 下多线程编程中的三个核心概念,可以作为面试中原因分析的讲解 :
(1)原子性的举例
(2)可见性的举例
(3)顺序性举例
】
解决办法:
#include#include #include boost::mutex mutex; int count = 0; void Counter() { mutex.lock(); int i = ++count; std::cout << "count == " << i << std::endl; // 前面代码如有异常,unlock 就调不到了。 mutex.unlock(); } int main() { // 创建一组线程。 boost::thread_group threads; for (int i = 0; i < 4; ++i) { threads.create_thread(&Counter); } // 等待所有线程结束。 threads.join_all(); return 0; }
加锁
使用互斥锁对多线程读写同一个shared_ptr进行加锁操作(多个线程访问同一资源时,为了保证数据的一致性,最简单的方式就是使用 mutex(互斥锁))
一旦一个线程获得了锁对象,那么在临界区时一直是受保护的,具体表现为该线程一直占着资源不放
2 。如果想测试这种是否安全,应该怎么做?
我的回答 : 打印 地址 get() 和 引用计数use_count()
3.c11的新特性有哪些?
1) long long 长整形,占8 个字节, 范围 -2^63 ~ 2^63 -1
列表初始化
{
2.1主要用于统一初始化语法 2.2注:初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,以一个新值替代
2.3 无论是类的变量 ,数组 , stl 的容器 ,类的构造 ,都统一使用{}
Liz例子: int *a =new int {3};
double b = double{12.12};
int *arr = new int[] {1,2,3};
new 的化就有空格, 不new 的话 就没有空格;
}
nullptr
{
主要用于代替NULL 传统c/C++会把NULL、o视为同一种东西,有些编译器会把NULL定义为((void")o),有些直接定义为0当需要使用NULL时直接养成使用nullptr的习惯
}
constexpr常量关键字
目的:
泛化常量表达式 在编译过程就能得到计算结果的表达式 可以用来修饰变量、函数、结构体等,目的是将运算尽可能放到编译阶段
一个重要的 :{
constexpr 只能定义编译期常量,而 const 可以定义编译期常量,也可以定义运行期常量。
void test() { const int a = 10; int b = 20; const int c = b; }
第 3 行定义一个编译期常量。
第 6 行定义了一个运行期常量。这是因为 b 是一个变量,编译期无法确定其值,必须到运行期才能确定。而 c 的值必须等到 b 确定才能确定,所以,c 是一个运行期常量。
上面第 6 行代码中的常量定义如果将 const 换成 constexpr ,则代码就会报语法错误,因为 constexpr 只能定义编译期常量。
说到常量有些同学可能不太清楚,程序中为什么要使用常量呢?有两点原因如下:
程序中总是需要一些不能修改的数据,写成变量则就有被意外修改的风险。
常量可以在编译期确定其值,某些场景下可以将一些计算任务放在编译阶段,从而提升程序效率。
}
别名2声明 : using ----示例 using INT =int ; 等价于 : typedef int INT;
类型 推断 : auto关键字 【C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器
,auto声明的变量必须由编译器在编译时期推导而得。
int TestAuto()
{
return 10;
}
int main()
{
int a = 10;
auto b = a;
auto c = 'a';
auto d = TestAuto();
cout << typeid(b).name() << endl;//这段代码为类型检查,会输出类型
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
//auto e; 无法通过编译,使用auto定义变量时必须对其进行初始化
return 0;
}
2.auto的使用细则
用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
int main()
{
int x = 10;
auto a = &x;
auto* b = &x;
auto& c = x;//引用类型
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
*a = 20;
*b = 30;
c = 40;
return 0;
}
2)在同一行定义多个变量
当在同一行声明多个变量时,这些变量必须是相同的类型
,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
遍历
前面 是 范围内 用于迭代的变量;
第二部分 倍 迭代的范围;
int a[10]={0,1,2,3,4,5,6,7,8,9};
for (auto e:a){
cout << e;
}
int main(){
int a[10]={0,1,2,3,4,5,6,7,8,9};
for (auto e : a)
{
++e;
cout << e;
}
return 0;
}
已同意
.1、auto关键字及用法 A、auto关键字能做什么?
4 -- 哈希表-- -红黑树区别?
时间复杂度,空间复杂度,操作复杂度,
回答: 1)什么是 hash
Hash,也可以称为“散列”,就是把任意长度的输入,通过散列算法,变换成固定长度的输出,该输出就是散列值。这是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出(也就是多对一的关系)。
2)哈希表的构造
在所有的线性数据结构中,数组的定位速度最快,因为它可通过数组下标直接定位到相应的数组空间,就不需要一个个查找.
而哈希表就是利用数组这个能够快速定位数据的结构解决以上的问题的。
"数组可以通过下标直接定位到相应的空间”,对就是这句,哈希表的做法其实很简单,就是把Key通过一 个固定的算法函数既所谓的哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标.
将value存储在以该数字为下标 的数组空间里,而当使用哈希表进行查询的时候,就是再次使用哈希函数将key转换为对应的数组下标,并定位到该空间获取value,如此一来,就可以充分 利用到数组的定位性能进行数据定位。
快速查找,删除的基本数据结构,通常需要总数据量可以放入内存。
开放地址法:
#include
#include
#include
#define MAX 7
#define NULL_VALUE -1
//哈希存储:开放地址法
//数据类型
typedef int DataType;
//哈希数组:存放数据
typedef struct
{
DataType *buf;//存放数据的数组首地址
int n;//表示有效元素个数
}Hash_table;
//1.0 创建空哈希表
Hash_table *create_empty_hash_table()
{
Hash_table *h = (Hash_table *)malloc(sizeof(Hash_table));
if(NULL == h)
{
printf("malloc is error\n");
return NULL;
}
memset(h, 0, sizeof(Hash_table));
//给哈希数组分配了MAX个元素的空间
h->buf = (DataType *)malloc(sizeof(DataType) * MAX);
if(NULL == h->buf)
{
printf("malloc is error\n");
return NULL;
}
memset(h->buf, NULL_VALUE, sizeof(DataType) * MAX);
h->n = 0;
return h;
}
//插入数据
// insert_data_hashtable(h, a[i]);
//key是要存储的数据
int insert_data_hashtable(Hash_table *h, DataType key)
{
int index = key % MAX;//要保存的下标
while(h->buf[index] != NULL_VALUE)
{
index = (index + 1) % MAX;//加1 向前找一个
}
//直到遇到-1 的空位就出去;
h->buf[index] = key;
h->n++;//有效数据加1
return 0;
}
//查找数据是否存在
int search_data_hash_table(Hash_table *h, DataType key)
{
int index = key % MAX;
while(h->buf[index] != key)
{
index = (index + 1) % MAX;
if(index == key % MAX || NULL_VALUE == h->buf[index])
{
return -1;
}
}
//表示while结束,找到要查找的数据
return index;
}
//打印哈希数组
void printf_hash_table(Hash_table *h)
{
int i = 0;
for(i = 0; i < MAX; i++)
{
printf("%d ", h->buf[i]);
}
putchar('\n');
return ;
}
int main(int argc, const char *argv[])
{
int a[MAX] = {10, 22, 13, 11, 24, 7, 14};
Hash_table *h = create_empty_hash_table();
int i = 0;
DataType data;
int ret = 0;
for(i = 0; i < MAX; i++)
{
insert_data_hashtable(h, a[i]);
}
printf_hash_table(h);
scanf("%d", &data);
ret = search_data_hash_table(h, data);
if(ret == -1)
{
printf("%d 不存在\n", data);
}
else
{
printf("%d 存在\n", data);
}
return 0;
}
第二 链地址 法
#include
#include
#include
//teacher
#define MAX 7
//链地址法
//数据类型
typedef int DataType;
typedef struct node
{
DataType data;
struct node *next;
}LinkNode;
LinkNode **create_empty_hashtable()
{
//给指针数组分配空间,指针数组里面每个元素是LinkNode *,一共有MAX个元素
LinkNode **h = (LinkNode **)malloc(sizeof(LinkNode *) * MAX);
int i = 0;
if(NULL == h)
{
printf("malloc is error\n");
return NULL;
}
for(i = 0; i < MAX; i++)
{
h[i] = NULL;
}
return h;
}
//存数据
int insert_data_hashtable(LinkNode **h, DataType key)
{
LinkNode *temp = (LinkNode *)malloc(sizeof(LinkNode));
int index = key % MAX;//存在的下标 ---> 头结点为h[index]
if(NULL == temp)
{
printf("malloc is error\n");
return -1;
}
memset(temp, 0, sizeof(LinkNode));
//类型于头查法
//更新temp
temp->data = key;// 数据域给过去
temp->next = h[index];//相当于 把NULL 给过去;
//在h[index]后面插入tmep
h[index] = temp;//把地址给过去
return 0;
}
//打印数组
void printf_hashtable(LinkNode **h)
{
int i = 0;
LinkNode *p = NULL;
for(i = 0; i < MAX; i++)
{
printf("--------%d-----------\n", i);
for(p = h[i]; p != NULL; p = p->next)
{
printf("%d ", p->data);
}
putchar('\n');
}
return ;
}
//查找数据
int serach_data_hashtable(LinkNode **h, DataType key)
{
int index = key % MAX;//头结点:h[index]
LinkNode *p = NULL;
for(p = h[index]; p != NULL; p = p->next)
{
if(p->data == key)
{
return index;
}
}
//表示没有找到数据,返回-1
return -1;
}
int main(int argc, const char *argv[])
{
DataType a[MAX] = {10, 22, 13, 11, 24, 7, 14};
LinkNode **h = create_empty_hashtable();
int i = 0;
DataType data = 0;
int ret = 0;
for(i = 0; i < MAX; i++)
{
insert_data_hashtable(h, a[i]);
}
//printf_hashtable(h);
scanf("%d", &data);
ret = serach_data_hashtable(h, data);
if(-1 == ret)
{
printf("%d 不存在\n", data);
}
else
{
printf("%d 存在\n", data);
}
return 0;
}
Map是c++标准库STL提供的一类关联式容器,提供key-value的存储和查找功能。
Map是基于红黑树的(同样set也是),那么它的查找速度是log(n)级别的。
它的优点是占用内存小。
权衡三个因素: 查找速度, 数据量, 内存使用,可扩展性,有序性。
总体来说,hash查找速度会比RB树快,而且查找速度基本和数据量大小无关,属于常数级别;;而RB树的查找速度是log(n)级别。
并不一定常数就比log(n) 小,因为hash还有hash函数的耗时。当元素达到一定数量级时,考虑hash。
但若你对内存使用特别严格, 希望程序尽可能少消耗内存,那么hash可能会让你陷入尴尬,特别是当你的hash对象特别多时,你就更无法控制了,而且 hash的构造速度较慢。
红黑树并不适应所有应用树的领域。如果数据基本上是静态的,那么让他们待在他们能够插入,并且不影响平衡的地方会具有更好的性能。如果数据完全是静态的,例如,做一个哈希表,性能可能会更好一些。
在实际的系统中,例如,需要使用动态规则的防火墙系统,使用红黑树而不是散列表被实践证明具有更好的伸缩性。Linux内核在管理vm_area_struct时就是采用了红黑树来维护内存块的
我的 总结:
红黑树是有序的,Hash是无序的,根据需求来选择。
红黑树占用的内存更小(仅需要为其存在的节点分配内存),而Hash事先就应该分配足够的内存存储散列表(即使有些槽可能遭弃用)
红黑树 查找和删除的时间复杂度都是O(logn),Hash查找和删除的时间复杂度都是O(1)。
补充:
如果只需要判断Map中某个值是否存在之类的操作,当然是Hash实现的要更加高效。
如果是需要将两个Map求并集交集差集等大量比较操作,就是红黑树实现的Map更加高效。
下集预告
5.cmake 使用
Modbus RTU: Modbus RTU(Remote Terminal Unit)是一种常用的串行通信协议,通常在低速串行通信介质(如 RS-232、RS-485)上使用。它使用二进制编码,在传输数据时将数据打包成连续的字节,并通过物理层发送。Modbus RTU 是一种简单且高效的协议,由于其在工业场景中的广泛应用,广受欢迎。它支持从设备的读取和写入操作,并可以支持多个从设备通过主设备进行控制和监控。
Modbus ASCII: Modbus ASCII 是 Modbus 的另一种变体,它是一个基于 ASCII 字符编码的串行通信协议。Modbus ASCII 使用可见字符来表示数据,并以字符间的间隔来区分不同的字节。相比于 Modbus RTU,Modbus ASCII 的数据传输速率较低,但它更易于调试和理解,因为数据以文本形式显示。Modbus ASCII 也支持读写操作,但由于其较低的传输速率,一般在较短距离的通信中使用。
Modbus TCP/IP: Modbus TCP/IP 是一种基于 TCP/IP 网络的 Modbus 变体。它使用以太网作为物理层,并通过 TCP/IP 网络进行数据的传输和通信。Modbus TCP/IP 具有较高的数据传输速率和较远的通信距离,能够实现在局域网(LAN)或广域网(WAN)上的远程通信。它支持主设备和多个从设备之间的读写操作,并且由于基于 TCP/IP,可以与其他网络设备进行集成和通信。
这三种常用的 Modbus 变体中,Modbus RTU 和 Modbus ASCII 是串行通信协议,适用于较短的通信距离和较低的数据传输速率。而 Modbus TCP/IP 是基于 TCP/IP 网络的协议,适用于远程通信和高速数据传输。具体选择哪种协议取决于通信环境、设备支持和应用要求。
8.1Modbus协议的固有问题
绝大多数工控协议在设计之初,仅仅考虑了功能实现、提高效率、提高可靠性等方面,而没考虑过安全性问题。Modbus协议也不例外,尽管其已经成为事实上的工业标准。从前面原理分析可以看出其本身的安全性问题是: 缺乏认证、授权、加密等安全防护机制和功能码滥用问题。
(1)缺乏认证
认证的目的是保证收到的信息来自合法的用户,未认证用户向设备发送控制命令不会被执行。在Modbus协议通信过程中,没有任何认证方面的相关定义,攻击者只需要找到一个合法的地址就可以使用功能码就能建立一个Modbus通信会话,从而扰乱整个或者部分控制过程。
(2)缺乏授权
授权是保证不同的特权操作需要由拥有不同权限的认证用户来完成,这样可大大降低误操作与内部攻击的概率。目前,Modbus协议没有基于角色的访问控制机制,也没有对用户分类,没有对用户的权限进行划分,这会导致任意用户可以执行任意功能。
(3)缺乏加密
加密可以保证通信过程中双方的信息不被第三方非法获取。Modbus协议通信过程中,地址和命令全部采用明文传输,因此数据可以很容易的被攻击者捕获和解析,为攻击者提供便利。
(4)功能码滥用
功能码是Modbus协议中的一项重要内容,几乎所有的通信都包含功能码。目前,功能码滥用是导致Modbus网络异常的一个主要因素。例如不合法报文长度,短周期的无用命令,不正确的报文长度,确认异常代码延迟等都有可能导致拒绝服务攻击。
8.2协议实现产生的问题
虽然Modbus协议获得了广泛的应用,但是在实现具体的工业控制系统时,开发者并不具备安全知识或者没有意识到安全问题。这样就导致了使用Modbus协议的系统中可能存在各种各样的安全漏洞。
(1)设计安全问题
Modbus系统开发者重点关注的是其功能实现问题,安全问题在设计时很少被注意到。设计安全是指设计时充分考虑安全性,解决Modbus系统可能出现的各种异常和非法操作等问题。比如在通信过程中,某个节点被恶意控制后发出非法数据,就需要考虑这些数据的判别和处理问题。
(2)缓冲区溢出漏洞
缓冲区溢出是指在向缓冲区内填充数据时超过了缓冲区本身的容量导致溢出的数据覆盖在合法数据上,这是在软件开发中最常见也是非常危险的漏洞,可以导致系统崩溃,或者被攻击者利用来控制系统。Modbus系统开发者大多不具备安全开发知识,这样就会产生很多的缓冲区溢出漏洞,一旦被恶意者利用会导致严重的后果。
(3)Modbus TCP安全问题
目前,Modbus协议已经可以在通用计算机和通用操作系统上实现,运行于TCP /IP之上以满足发展需要。这样,TCP/IP协议自身存在的安全问题不可避免地会影响到工控网络安全。非法网络数据获取,中间人,拒绝服务,IP欺骗,病毒木马等在IP互联网中的常用攻击手段都会影响Modbus系统安全。
8.3安全建议
目前,Modbus系统采取的安全防护措施普遍不足,这里参考信息安全业内研究并结合工控系统自身的安全问题,提出了一些安全建议,能够有效地降低工业控制系统面临的威胁。
(1)从源头开始
工控网络漏洞,很大一部分是其实现过程出现的漏洞。如果从源头开始控制,从Modbus系统的需求设计、开发实现、内部测试和部署等阶段,全生命周期的介入安全手段,融入安全设计、安全编码以及安全测试等技术,可以极大地消除安全漏洞,降低整个Modbus系统的安全风险。
(2)异常行为检测
异常行为代表着可能发生威胁,不管是有没有攻击者,因此开发针对Modbus系统的专用异常行为检测设备可以极大提高工控网络的安全性。针对Modbus系统,首先要分析其存在的各种操作行为,依据“主体,地点,时间,访问方式,操作,客体”等行为描述成一个六元组模型; 进而分析其行为是否属于异常; 最终决定采取记录或者报警等措施。
(3)安全审计
Modbus的安全审计就是对协议数据进行深度解码分析,记录操作的时间、地点、操作者和操作行为等关键信息,实现对Modbus系统的安全审计日志记录和审计功能,从而提供安全事件爆发后的时候追查能力。
(4)使用网络安全设备
使用入侵防御和防火墙等网络安全设备。防火墙是一个串行设备,通过设置,只允许特定的地址访问服务端,禁止外部地址访问Modbus服务器,可以有效的防止外部入侵;入侵防御设备可以分析Modbus协议的具体操作内容,有效地检测并阻止来自内部/外部的异常操作和各种渗透攻击行为,对内网提供保护功能。