uvm源码解读-sequence,sequencer,driver三者之间的握手关系1

1.start item

1.start_item();sequencer.wait_for_grant(prior);this.pre_do(1);

需要指出,这里明确说明了wait_for_grant和send_request之间不能有任何延迟,所以在mid_do这个任务里千万不能有任何延迟。 

task uvm_sequencer_base::wait_for_grant(uvm_sequence_base sequence_ptr,int item_priority = -1,bit lock_request = 0);uvm_sequence_request req_s;int my_seq_id;if (sequence_ptr == null)uvm_report_fatal("uvm_sequencer","wait_for_grant passed null sequence_ptr", UVM_NONE);my_seq_id = m_register_sequence(sequence_ptr);// If lock_request is asserted, then issue a lock.  Don't wait for the response, since// there is a request immediately following the lock requestif (lock_request == 1) beginreq_s = new();req_s.grant = 0;req_s.sequence_id = my_seq_id;req_s.request = SEQ_TYPE_LOCK;req_s.sequence_ptr = sequence_ptr;req_s.request_id = g_request_id++;req_s.process_id = process::self();arb_sequence_q.push_back(req_s);end// Push the request onto the queuereq_s = new();req_s.grant = 0;req_s.request = SEQ_TYPE_REQ;req_s.sequence_id = my_seq_id;req_s.item_priority = item_priority;req_s.sequence_ptr = sequence_ptr;req_s.request_id = g_request_id++;req_s.process_id = process::self();arb_sequence_q.push_back(req_s);m_update_lists();// Wait until this entry is granted// Continue to point to the element, since location in queue will changem_wait_for_arbitration_completed(req_s.request_id);// The wait_for_grant_semaphore is used only to check that send_request// is only called after wait_for_grant.  This is not a complete check, since// requests might be done in parallel, but it will catch basic errorsreq_s.sequence_ptr.m_wait_for_grant_semaphore++;//这个变量会用在send_request那里,发了一个减少一个,这里得到一个grant加一个endtask

需要注意几点:

(1)uvm_sequence_base,相当于一个seq或者item的合集

(2) uvm_sequence_request

class uvm_sequence_request;bit        grant;int        sequence_id;int        request_id;int        item_priority;process    process_id;uvm_sequencer_base::seq_req_t  request;uvm_sequence_base sequence_ptr;
endclass

(3)m_register_sequence,这个任务核心就是输出sequence_ptr的id,同时将sequence_ptr记录到自己定义的动态数组reg_sequence中

function int uvm_sequencer_base::m_register_sequence(uvm_sequence_base sequence_ptr);if (sequence_ptr.m_get_sqr_sequence_id(m_sequencer_id, 1) > 0)return sequence_ptr.get_sequence_id();sequence_ptr.m_set_sqr_sequence_id(m_sequencer_id, g_sequence_id++);reg_sequences[sequence_ptr.get_sequence_id()] = sequence_ptr;return sequence_ptr.get_sequence_id();
endfunction-------------------------------------------------------------------------------
m_get_sqr_sequence_id主要做的内容就是:定义了一个存放id的动态数组m_sqr_seq_ids,如果这个动态数组中存在输入的id,同时需要更新这个id,那就将这个id更新到对应item中function int m_get_sqr_sequence_id(int sequencer_id, bit update_sequence_id);if (m_sqr_seq_ids.exists(sequencer_id)) beginif (update_sequence_id == 1) beginset_sequence_id(m_sqr_seq_ids[sequencer_id]);endreturn m_sqr_seq_ids[sequencer_id];endif (update_sequence_id == 1)set_sequence_id(-1);return -1;endfunction
------------------------------------------------------------------------------------
function void set_sequence_id(int id);//uvm_sequence_item中的任务m_sequence_id = id;//这个m_sequence_id是item的idendfunction
------------------------------------------------------------------------------------function int get_sequence_id();//get_sequence_id是uvm_sequence_item中的任务return (m_sequence_id);//就是返回当前item的idendfunction
------------------------------------------------------------------------------------reg_sequences是定义的uvm_sequence_base的动态数组-> protected uvm_sequence_base   reg_sequences[int];
------------------------------------------------------------------------------------

 (4)

protected uvm_sequence_request arb_sequence_q[$];

(5)

function void uvm_sequencer_base::m_update_lists();m_lock_arb_size++;
endfunction

 (6)注意这里有几个变量,第一个变量是lock_arb_size,第二个变量是m_lock_arb_size变量,m_lock_arb_size这个变量在每次进行m_update_lists()任务的时候会加一次,在一开始的时候会将这两个值相当,随后去等执行m_update_lists()任务。注意这里的arb_completed,原型是  protected bit  arb_completed[int]; 删除这个index相当于从仲裁的q(arb_completed)将仲裁完毕的id删除。

task uvm_sequencer_base::m_wait_for_arbitration_completed(int request_id);int lock_arb_size;// Search the list of arb_wait_q, see if this item is doneforever beginlock_arb_size  = m_lock_arb_size;if (arb_completed.exists(request_id)) beginarb_completed.delete(request_id);return;endwait (lock_arb_size != m_lock_arb_size);end
endtask

2.finish_item

finish_item();
(1)this.mid_do(item);
(2)sequencer.send_request(item);
(3)sequencer.wait_for_item_done();
(4)this.post_do(item);

注意,实际上会存在三个fifo,一个fifo是 uvm_tlm_fifo #(REQ) m_req_fifo;另两个是

 REQ m_last_req_buffer[$];
  RSP m_last_rsp_buffer[$];

uvm_send实际上是不断的往m_req_fifo去放入内容。

function void uvm_sequencer_param_base::send_request(uvm_sequence_base sequence_ptr,uvm_sequence_item t,bit rerandomize = 0);REQ param_t;if (sequence_ptr == null) beginuvm_report_fatal("SNDREQ", "Send request sequence_ptr is null", UVM_NONE);end//没有的化报fatalif (sequence_ptr.m_wait_for_grant_semaphore < 1) beginuvm_report_fatal("SNDREQ", "Send request called without wait_for_grant", UVM_NONE);end//注意,在上面wait_for_grant任务中得到一个grant就会加一个,这里减少sequence_ptr.m_wait_for_grant_semaphore--;if ($cast(param_t, t)) beginif (rerandomize == 1) begin//如果给了随机的选项,需要进行随机if (!param_t.randomize()) beginuvm_report_warning("SQRSNDREQ", "Failed to rerandomize sequence item in send_request");endendif (param_t.get_transaction_id() == -1) beginparam_t.set_transaction_id(sequence_ptr.m_next_transaction_id++);endm_last_req_push_front(param_t);//往别的q里去推end else beginuvm_report_fatal(get_name(),$sformatf("send_request failed to cast sequence item"), UVM_NONE);endparam_t.set_sequence_id(sequence_ptr.m_get_sqr_sequence_id(m_sequencer_id, 1));t.set_sequencer(this);if (m_req_fifo.try_put(param_t) != 1) beginuvm_report_fatal(get_full_name(), "Concurrent calls to get_next_item() not supported. Consider using a semaphore to ensure that concurrent processes take turns in the driver", UVM_NONE);endm_num_reqs_sent++;//记录send了多少内容// Grant any locks as soon as possiblegrant_queued_locks();
endfunction

grant_queued_locks()任务有点复杂,没看完,留个坑

注意这个任务 ,前期会一直往m_last_req_buffer里push,注意这里是从前推入。这个q是uvm_sequencer_param_base类中定义的q,他的类型采用了参数化的类REQ,定义方式如下:

REQ m_last_req_buffer[$];

function void uvm_sequencer_param_base::m_last_req_push_front(REQ item);if(!m_num_last_reqs)return;if(m_last_req_buffer.size() == m_num_last_reqs)void'(m_last_req_buffer.pop_back());this.m_last_req_buffer.push_front(item);
endfunction

 其中m_num_last_reqs这个变量是通过set_num_last_reqs任务实现的,也就是说m_last_req_buffer的buf含量最大是1024个。

function void uvm_sequencer_param_base::set_num_last_reqs(int unsigned max);if(max > 1024) beginuvm_report_warning("HSTOB", $sformatf("Invalid last size; 1024 is the maximum and will be used"));max = 1024;end//shrink the buffer if necessarywhile((m_last_req_buffer.size() != 0) && (m_last_req_buffer.size() > max))void'(m_last_req_buffer.pop_back());m_num_last_reqs = max;num_last_items = max;endfunction

这里就有个问题了,可以看到,实际上源码 并没有用m_last_req_buffer,而是用的是m_req_fifo,那这个buffer到底是做什么用的呢?其实查阅uvm手册可以发现,针对m_last_req_buffer的相关任务大多是以API的形式存在,所以个人理解还是封装了一下以便上层可以根据API实现更为负载的sequencer。

下面介绍结构和m_last_req_buffer/m_last_rsp_buffer相关的方法,可以在sequencer中实现。

(1)last_req,返回最后一个req,虽然叫last_req,但是实际可以返回任何顺序的。

function REQ last_req(int unsigned n = 0);if(n > m_num_last_reqs) beginuvm_report_warning("HSTOB",$sformatf("Invalid last access (%0d), the max history is %0d", n,m_num_last_reqs));return null;endif(n == m_last_req_buffer.size())return null;return m_last_req_buffer[n];
endfunction

get_num_last_reqs,相当于返回m_last_req_buffer的size

function int unsigned uvm_sequencer_param_base::get_num_last_reqs();return m_num_last_reqs;
endfunction

 set_num_last_reqs,约束m_last_req_buffer的size,多的部分会被pop出去。

function void uvm_sequencer_param_base::set_num_last_reqs(int unsigned max);if(max > 1024) beginuvm_report_warning("HSTOB", $sformatf("Invalid last size; 1024 is the maximum and will be used"));max = 1024;end//shrink the buffer if necessarywhile((m_last_req_buffer.size() != 0) && (m_last_req_buffer.size() > max))void'(m_last_req_buffer.pop_back());m_num_last_reqs = max;num_last_items = max;endfunction

 m_last_rsp_buffer的方法和上面类似,但是方法名称有所不同,分别是last_rsp();set_num_last_rsps();get_num_last_rsps();

 

function int uvm_sequencer_param_base::get_num_rsps_received();return m_num_rsps_received;//相当于m_last_rsp_buffer的size
endfunction

wait_for_item_done针对这个sequence_ptr,如果没有定义了transaction_id,那么仅仅需要等sequence_id,如果定义了的化,还需要多等一个 transaction_id。

task uvm_sequencer_base::wait_for_item_done(uvm_sequence_base sequence_ptr,int transaction_id);int sequence_id;sequence_id = sequence_ptr.m_get_sqr_sequence_id(m_sequencer_id, 1);m_wait_for_item_sequence_id = -1;m_wait_for_item_transaction_id = -1;if (transaction_id == -1)wait (m_wait_for_item_sequence_id == sequence_id);elsewait ((m_wait_for_item_sequence_id == sequence_id &&m_wait_for_item_transaction_id == transaction_id));
endtask

 3.uvm_do做了上面的两部分:

4.driver,这里介绍和driver相关的两个方法:(1)get_next_item(2)try_next_item

需要注意的是,这两个方法都是是从前面说的m_req_fifo里去拿。

注意这里get_next_item任务,在执行这个任务的时候,get_next_item_called变量置为1,这个变量会在item_done的任务里再置为0,这个变量是为了避免多次get item,但是不执行item_done的情况。 

task uvm_sequencer::get_next_item(output REQ t);REQ req_item;// If a sequence_item has already been requested, then get_next_item()// should not be called again until item_done() has been called.if (get_next_item_called == 1)uvm_report_error(get_full_name(),"Get_next_item called twice without item_done or get in between", UVM_NONE);if (!sequence_item_requested)m_select_sequence();// Set flag indicating that the item has been requested to ensure that item_done or get// is called between requestssequence_item_requested = 1;get_next_item_called = 1;m_req_fifo.peek(t);
endtask

 try_next_item

task uvm_sequencer::try_next_item(output REQ t);int selected_sequence;time arb_time;uvm_sequence_base seq;if (get_next_item_called == 1) beginuvm_report_error(get_full_name(), "get_next_item/try_next_item called twice without item_done or get in between", UVM_NONE);return;end//前面就是做一些列的判断,防止出现多次get item的情况// allow state from last transaction to settle such that sequences'// relevancy can be determined with up-to-date informationwait_for_sequences();//这个任务实际上就是等一个时间片// choose the sequence based on relevancyselected_sequence = m_choose_next_request();//仲裁出到底使用哪个sequence// return if none availableif (selected_sequence == -1) begint = null;return;end// now, allow chosen sequence to resumem_set_arbitration_completed(arb_sequence_q[selected_sequence].request_id);seq = arb_sequence_q[selected_sequence].sequence_ptr;arb_sequence_q.delete(selected_sequence);m_update_lists();sequence_item_requested = 1;get_next_item_called = 1;// give it one NBA to put a new item in the fifowait_for_sequences();// attempt to get the item; if it fails, produce an error and returnif (!m_req_fifo.try_peek(t))uvm_report_error("TRY_NEXT_BLOCKED", {"try_next_item: the selected sequence '",seq.get_full_name(), "' did not produce an item within an NBA delay. ","Sequences should not consume time between calls to start_item and finish_item. ","Returning null item."}, UVM_NONE);endtask

 具体看m_choose_next_request()任务

function int uvm_sequencer_base::m_choose_next_request();int i, temp;int avail_sequence_count;int sum_priority_val;integer avail_sequences[$];integer highest_sequences[$];int highest_pri;string  s;avail_sequence_count = 0;grant_queued_locks();i = 0;while (i < arb_sequence_q.size()) beginif ((arb_sequence_q[i].process_id.status == process::KILLED) ||(arb_sequence_q[i].process_id.status == process::FINISHED)) begin`uvm_error("SEQREQZMB", $sformatf("The task responsible for requesting a wait_for_grant on sequencer '%s' for sequence '%s' has been killed, to avoid a deadlock the sequence will be removed from the arbitration queues", this.get_full_name(), arb_sequence_q[i].sequence_ptr.get_full_name()))remove_sequence_from_queues(arb_sequence_q[i].sequence_ptr);continue;endif (i < arb_sequence_q.size())if (arb_sequence_q[i].request == SEQ_TYPE_REQ)if (is_blocked(arb_sequence_q[i].sequence_ptr) == 0)if (arb_sequence_q[i].sequence_ptr.is_relevant() == 1) beginif (m_arbitration == UVM_SEQ_ARB_FIFO) beginreturn i;//如果是默认的仲裁方式,实际上在这里就会返回ID号endelse avail_sequences.push_back(i);endi++;end//这个任务实际就是在seq不是lock的,同时这个seq是有效的(is_relevant=1)的情况下,如果不是fifo的仲裁方式的化,将arb_sequence_q中的索引值推入到avail_sequences中去。// Return immediately if there are 0 or 1 available sequencesif (m_arbitration == UVM_SEQ_ARB_FIFO) beginreturn -1;endif (avail_sequences.size() < 1)  begin//这两个函数我理解只是做保护的return -1;endif (avail_sequences.size() == 1) begin//size为1的时候立刻返回return avail_sequences[0];end// If any locks are in place, then the available queue must// be checked to see if a lock prevents any sequence from proceedingif (lock_list.size() > 0) begin//这个函数没懂for (i = 0; i < avail_sequences.size(); i++) beginif (is_blocked(arb_sequence_q[avail_sequences[i]].sequence_ptr) != 0) beginavail_sequences.delete(i);i--;endendif (avail_sequences.size() < 1)return -1;if (avail_sequences.size() == 1)return avail_sequences[0];end//  Weighted Priority Distribution// Pick an available sequence based on weighted priorities of available sequences
//权重约束,这里我理解是把所有seq的权重加在一起得到一个总的门槛值,然后随机一个在门槛值和0之间的值,如果当前seq的权重大于这个随机值,就认为仲裁成功,在此后的循环过程中,新的seq的权重总是会加上之前seq的权重,把这个权重的和与门槛值进行比较。if (m_arbitration == UVM_SEQ_ARB_WEIGHTED) beginsum_priority_val = 0;for (i = 0; i < avail_sequences.size(); i++) beginsum_priority_val += m_get_seq_item_priority(arb_sequence_q[avail_sequences[i]]);endtemp = $urandom_range(sum_priority_val-1, 0);sum_priority_val = 0;for (i = 0; i < avail_sequences.size(); i++) beginif ((m_get_seq_item_priority(arb_sequence_q[avail_sequences[i]]) + sum_priority_val) > temp) beginreturn avail_sequences[i];endsum_priority_val += m_get_seq_item_priority(arb_sequence_q[avail_sequences[i]]);enduvm_report_fatal("Sequencer", "UVM Internal error in weighted arbitration code", UVM_NONE);end//这里相当于随机给一个//  Random Distributionif (m_arbitration == UVM_SEQ_ARB_RANDOM) begini = $urandom_range(avail_sequences.size()-1, 0);return avail_sequences[i];end//  Strict Fifoif ((m_arbitration == UVM_SEQ_ARB_STRICT_FIFO) || m_arbitration == UVM_SEQ_ARB_STRICT_RANDOM) beginhighest_pri = 0;// Build a list of sequences at the highest priority
//这个for循环相当于找最大优先级的seq,把最大优先级的seq都放到highest_sequences,注意这里的delete动作,保证了只会存在最大优先级的seqfor (i = 0; i < avail_sequences.size(); i++) beginif (m_get_seq_item_priority(arb_sequence_q[avail_sequences[i]]) > highest_pri) begin// New highest priority, so start new listhighest_sequences.delete();highest_sequences.push_back(avail_sequences[i]);highest_pri = m_get_seq_item_priority(arb_sequence_q[avail_sequences[i]]);endelse if (m_get_seq_item_priority(arb_sequence_q[avail_sequences[i]]) == highest_pri) beginhighest_sequences.push_back(avail_sequences[i]);endend
//如果是严格fifo,会把最大优先级的,同时最先进入fifo的,先给出出去// Now choose one based on arbitration typeif (m_arbitration == UVM_SEQ_ARB_STRICT_FIFO) beginreturn(highest_sequences[0]);end
//如果不是严格fifo的化,就是在最大优先级的各个seq里去随机    i = $urandom_range(highest_sequences.size()-1, 0);return highest_sequences[i];end
//这个仲裁没看if (m_arbitration == UVM_SEQ_ARB_USER) begini = user_priority_arbitration( avail_sequences);// Check that the returned sequence is in the list of available sequences.  Failure to// use an available sequence will cause highly unpredictable results.highest_sequences = avail_sequences.find with (item == i);if (highest_sequences.size() == 0) beginuvm_report_fatal("Sequencer",$sformatf("Error in User arbitration, sequence %0d not available\n%s",i, convert2string()), UVM_NONE);endreturn(i);enduvm_report_fatal("Sequencer", "Internal error: Failed to choose sequence", UVM_NONE);endfunction

 

未完。。。。 

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

OpenCV实现图像边缘检测(sobel算子、laplacian算子、Canny边缘检测)

边缘检测原理 1&#xff0c;Sobel算子 1.1代码实现 import cv2 as cv import numpy as np import matplotlib.pyplot as plt from pylab import mplmpl.rcParams[font.sans-serif] [SimHei]img cv.imread("lena.png",0)#计算sobel卷积结果 x cv.Sobel(img,cv.CV_…

MySQL数据库详解 二:数据库的高级语言和操作

文章目录 1. 克隆表 ---- 将数据表的数据记录生成到新的表中1.1 方式一&#xff1a;先创建新表&#xff0c;再导入数据1.2方式二&#xff1a;创建的时候同时导入 2. 清空表 ---- 删除表内的所有数据2.1 delete删除2.2 truncate删除&#xff08;重新记录&#xff09;2.3 创建临时…

【刷题篇】回溯算法(深度优先搜索(一))

文章目录 无重复字符串的排列组合员工的重要性图像渲染被围绕的区域 无重复字符串的排列组合 无重复字符串的排列组合。编写一种方法&#xff0c;计算某字符串的所有排列组合&#xff0c;字符串每个字符均不相同。 class Solution { public:void DFS(string &s,vector<s…

记一次失败的pip使用经历

python如何使用pip工具下载第三方库&#xff1f; 首先&#xff0c;安装并配置好python和pip的环境&#xff0c;特别注意pip放在python的script文件下&#xff0c;有pip和pip3两种&#xff0c;选择pip3版本。如下图所示。 然后打开命令行窗口&#xff0c;检查python和pip工具是…

Android之MediaCodec::PostAndAwaitResponse消息原理(四十三)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药. 更多原创,欢迎关注:Android…

Python异步编程并发执行爬虫任务,用回调函数解析响应

一、问题&#xff1a;当发送API请求&#xff0c;读写数据库任务较重时&#xff0c;程序运行效率急剧下降。 异步技术是Python编程中对提升性能非常重要的一项技术。在实际应用&#xff0c;经常面临对外发送网络请求&#xff0c;调用外部接口&#xff0c;或者不断更新数据库或文…

Vue 的组件加载顺序和渲染顺序

1、结论先行 组件的加载顺序是自上而下的&#xff0c;也就是先加载父组件&#xff0c;再递归地加载其所有的子组件。 而组件渲染顺序是按照深度优先遍历的方式&#xff0c;也就是先渲染最深层的子组件&#xff0c;再依次向上渲染其父组件。 2、案例 下面是一个简单的示例代…

Ingress Controller

什么是 Ingress Controller &#xff1f; 在云原生生态中&#xff0c;通常来讲&#xff0c;入口控制器( Ingress Controller )是 Kubernetes 中的一个关键组件&#xff0c;用于管理入口资源对象。 Ingress 资源对象用于定义来自外网的 HTTP 和 HTTPS 规则&#xff0c;以控制进…

【C语言】字符函数和字符串函数(含模拟)

前言&#xff1a; 在做OJ题或阅读代码时或多或少会遇到一些字符函数和字符串函数&#xff0c; 如果不认识或不熟悉就会造成不便&#xff0c; 本篇文章主要是为了这方面而存在&#xff0c; 此篇介绍各个字符串的功能与使用方法&#xff0c; 下一篇会讲解如何模拟这些函数 重点&a…

Java Fluent编程

背景 Fluent Api最早是由Martin Fowler1提出的一种简洁的编程风格, 其每一步的执行都返回一个对象并可用于进一步的方法调用. 这样的编程风格可以很好的简化某些领域的开发, 并显著地提高代码的可读性和可维护性. 无论是在Java的流式api中, 还是众多DLS中都有它的身影. 原因主…

备受以太坊基金会青睐的 Hexlink,构建亿级用户涌入 Web3的入口

早在 2021 年 9 月&#xff0c;以太坊创始人 Vitalik Buterin 就曾提出了 EIP-4337&#xff08;账户抽象&#xff09;提案&#xff0c;并在去年 10 月对该提案进一步更新&#xff0c;引发行业的进一步关注。在今年 3 月&#xff0c;EIP-4337 提案正式通过审计&#xff0c;并成为…

SpringBean的生命周期

SpringBean的生命周期 SperingBean的生命周期是从Bean实例化之后&#xff0c;即通过反射创建出对象之后&#xff0c;到Bean成为一个完整对象&#xff0c;最终存储到单例池中&#xff0c;这个过程被称为Spring Bean的生命周期。Spring Bean的生命周期大体上分为三个阶段 Bean的…

初识Java 10-3 集合

目录 Collection和Iterator的对比 for-in和迭代器 总结图 本笔记参考自&#xff1a; 《On Java 中文版》 Collection和Iterator的对比 Collection是所有序列集合的共同根接口。因此&#xff0c;可以认为它是一个为表示其他接口之间的共性而出现的“附属接口”。 java.util.Ab…

Git大全

目录 一、Git概述 1.1Git简介 1.2Git工作流程图 1.3查看Git的版本 1.4 Git 使用前配置 1.5为常用指令配置别名&#xff08;可选&#xff09; 1.5.1打开用户目录&#xff0c;创建 .bashrc 文件 1.5.2在 .bashrc 文件中输入如下内容&#xff1a; 1.5.3打开gitBash&#xff0c;执行…

C++:类中的静态成员函数以及静态成员变量

一、静态成员变量 静态成员&#xff1a;在类定义中&#xff0c;它的成员&#xff08;包括成员变量和成员函数&#xff09;&#xff0c;这些成员可以用关键字static声明为静态的&#xff0c;称为静态成员。 静态成员变量需要在类外分配空间&#xff0c;static 成员变量是在初始…

【解决方案】edge浏览器批量添加到集锦功能消失的解决方案

edge的集锦功能很好用&#xff0c;右键标签页会出现如下选项&#xff1a; 但在某次edge更新后&#xff0c;右键标签页不再出现该选项&#xff1a; 这里可以参考为什么我的Edge浏览器右键标签页没有“将所有标签页添加到集锦”功能&#xff1f; - Microsoft Community 一文提出…

pcl--第十节 点云曲面重建

曲面重建技术在逆向工程、数据可视化、机器视觉、虚拟现实、医疗技术等领域中得到了广泛的应用 。 例如&#xff0c;在汽车、航空等工业领域中&#xff0c;复杂外形产品的设计仍需要根据手工模型&#xff0c;采用逆向工程的手段建立产品的数字化模型&#xff0c;根据测量数据建…

超级好用绘图工具(Draw.io+Github)

超级好用绘图工具&#xff08;Draw.ioGithub&#xff09; 方案简介 绘图工具&#xff1a;Draw.io 存储方式&#xff1a; Github 1 Draw.io 1.2 简介 ​ 是一款免费开源的在线流程图绘制软件&#xff0c;可以用于创建流程图、组织结构图、网络图、UML图等各种类型的图表。…

windows server 2019 、2012等服务器查看系统和应用程序日志

查看windows系统日志 点击左下角的windows按钮&#xff0c;输入事件两个字&#xff0c;会显示时间查看器 点击事件查看器&#xff0c;windows日志下面可以卡到系统日志和应用程序的日志 筛选时间范围内的日志 修改记录时间 选组自定义范围 选择事件事件 输入事件范围&#xff…

C语言大佬的必杀技---宏的高级用法

C语言大佬的必杀技—宏的高级用法 目录: 字符串化标记的拼接宏的嵌套替换多条语句防止一个文件被重复包含宏和函数的区别 可能大家在学习的时候用得比较少&#xff0c;但是在一些代码量比较大的时候&#xff0c;这样使用&#xff0c;可以大大的提高代码的可读性&#xff0c;…