嵌入式开发中的防御性C语言编程

2023-05-16

嵌入式产品的可靠性自然与硬件密不可分,但在硬件确定、并且没有第三方测试的前提下,使用防御性编程思想写出的代码,往往具有更高的稳定性。

防御性编程首先需要认清C语言的种种缺陷和陷阱,C语言对于运行时的检查十分弱小,需要程序员谨慎的考虑代码,在必要的时候增加判断;防御性编程的另一个核心思想是假设代码运行在并不可靠的硬件上,外接干扰有可能会打乱程序执行顺序、更改RAM存储数据等等。

1 具有形参的函数,需判断传递来的实参是否合法

程序员可能无意识的传递了错误参数;外界的强干扰可能将传递的参数修改掉,或者使用随机参数意外的调用函数,因此在执行函数主体前,需要先确定实参是否合法。

图片

2 仔细检查函数的返回值

对函数返回的错误码,要进行全面仔细处理,必要时做错误记录。

图片

3 防止指针越界

如果动态计算一个地址时,要保证被计算的地址是合理的并指向某个有意义的地方。特别对于指向一个结构或数组的内部的指针,当指针增加或者改变后仍然指向同一个结构或数组。

4 防止数组越界

数组越界的问题前文已经讲述的很多了,由于C不会对数组进行有效的检测,因此必须在应用中显式的检测数组越界问题。下面的例子可用于中断接收通讯数据。

图片

在使用一些库函数时,同样需要对边界进行检查,比如下面的memset(RecBuf,0,len)函数把RecBuf指指向的内存区的前len个字节用0填充,如果不注意len的长度,就会将数组RecBuf之外的内存区清零:

图片

5 数学数运算

5**.1除法运算,只检测除数为零就可靠吗?**

除法运算前,检查除数是否为零几乎已经成为共识,但是仅检查除数是否为零就够了吗?

考虑两个整数相除,对于一个signed long类型变量,它能表示的数值范围为:-2147483648 ~+2147483647,如果让-2147483648/ -1,那么结果应该是+2147483648,但是这个结果已经超出了signedlong所能表示的范围了。所以,在这种情况下,除了要检测除数是否为零外,还要检测除法是否溢出。

 #include <limits.h>
 signed long sl1,sl2,result;
 /*初始化sl1和sl2*/    
 if((sl2==0)||(sl1==LONG_MIN && sl2==-1))
 {
     //处理错误    
 }
 else   
 {
     result = sl1 / sl2;
 }

5**.2检测运算溢出**

整数的加减乘运算都有可能发生溢出,在讨论未定义行为时,给出过一个有符号整形加法溢出判断代码,这里再给出一个无符号整形加法溢出判断代码段:

#include <limits.h>    
unsigned int a,b,result;
/*初始化a,b*/
if(UINT_MAX-a<b)
 {
//处理溢出    
 }
else
 {
     result=a+b;
 }

嵌入式硬件一般没有浮点处理器,浮点数运算在嵌入式也比较少见并且溢出判断严重依赖C库支持,这里不讨论。

5.3检测移****位

在讨论未定义行为时,提到有符号数右移、移位的数量是负值或者大于操作数的位数都是未定义行为,也提到不对有符号数进行位操作,但要检测移位的数量是否大于操作数的位数。下面给出一个无符号整数左移检测代码段:

unsigned int ui1;
unsigned int ui2;
unsigned int uresult;
/*初始化ui1,ui2*/
if(ui2>=sizeof(unsigned int)*CHAR_BIT)
 {
//处理错误  
 }
else
 {
     uresult=ui1<<ui2;
 }

6 如果有硬件看门狗,则使用它

在其它一切措施都失效的情况下,看门狗可能是最后的防线。它的原理特别简单,但却能大大提高设备的可靠性。如果设备有硬件看门狗,一定要为它编写驱动程序。相关推荐:STM32实例-窗口看门狗实验。

  • 要尽可能早的开启看门狗

    这是因为从上电复位结束到开启看门狗的这段时间内,设备有可能被干扰而跳过看门狗初始化程序,导致看门狗失效。尽可能早的开启看门狗,可以降低这种概率;

  • 不要在中断中喂狗,除非有其他联动措施

    在中断程序喂狗,由于干扰的存在,程序可能一直处于中断之中,这样会导致看门狗失效。如果在主程序中设置标志位,中断程序喂狗时与这个标志位联合判断,也是允许的;

  • 喂狗间隔跟产品需求有关,并非特定的时间

    产品的特性决定了喂狗间隔。对于不涉及安全性、实时性的设备,喂狗间隔比较宽松,但间隔时间不宜过长,否则被用户感知到,是影响用户体验的。对于设计安全性、有实时控制类的设备,原则是尽可能快的复位,否则会造成事故。

    克莱门汀号在进行第二阶段的任务时,原本预订要从月球飞行到太空深处的Geographos小行星进行探勘,然而这艘太空探测器在飞向小行星时却由于一个软件缺陷而使其中断运作20分钟,不但未能到达小行星,也因为控制喷嘴燃烧了11分钟使电力供应降低,无法再透过远端控制探测器,最终结束这项任务,但也导致了资源与资金的浪费。

    “克莱门汀太空任务失败这件事让我感到十分震惊,它其实可以透过硬件中一款简单的看门狗计时器避免掉这项意外,但由于当时的开发时间相当紧缩,程序设计人员没时间编写程序来启动它,”Ganssle说。

    遗憾的是,1998年发射的近地号太空船(NEAR)也遇到了相同的问题。由于编程人员并未采纳建议,因此,当推进器减速器系统故障时,29公斤的储备燃料也随之报销──这同样是一个本来可经由看门狗定时器编程而避免的问题,同时也证明要从其他程序设计人员的错误中学习并不容易。

7****关键数据储存多个备份,取数据采用“表决法”

RAM中的数据在受到干扰情况下有可能被改变,对于系统关键数据应该进行保护。关键数据包括全局变量、静态变量以及需要保护的数据区域。备份数据与原数据不应该处于相邻位置,因此不应由编译器默认分配备份数据位置,而应该由程序员指定区域存储。相关文章:单片机中的RAM vs ROM。

可以将RAM分为3个区域,第一个区域保存原码,第二个区域保存反码,第三个区域保存异或码,区域之间预留一定量的“空白”RAM作为隔离。可以使用编译器的“分散加载”机制将变量分别存储在这些区域。需要进行读取时,同时读出3份数据并进行表决,取至少有两个相同的那个值。

假如设备的RAM从0x1000_0000开始,我需要在RAM的0x1000_00000x10007FFF内存储原码,在0x1000_90000x10009FFF内存储反码,在0x1000_B000~0x1000BFFF内存储0xAA的异或码,编译器的分散加载可以设置为:

 LR_IROM1 0x00000000 0x00080000  {    ; load region size_region
   ER_IROM1 0x00000000 0x00080000  {  ; load address = execution address
    *.o (RESET, +First)
    *(InRoot$$Sections)
    .ANY (+RO)
   }
   RW_IRAM1 0x10000000 0x00008000  {  ;保存原码
    .ANY (+RW +ZI )
   }
   RW_IRAM3 0x10009000 0x00001000{    ;保存反码
    .ANY (MY_BK1)
   }
   RW_IRAM2 0x1000B000 0x00001000  {  ;保存异或码
    .ANY (MY_BK2)
   }
 }

如果一个关键变量需要多处备份,可以按照下面方式定义变量,将三个变量分别指定到三个不连续的RAM区中,并在定义时按照原码、反码、0xAA的异或码进行初始化。

 uint32  plc_pc=0;                                                       //原码  
 __attribute__((section("MY_BK1"))) uint32 plc_pc_not=~0x0;              //反码  
 __attribute__((section("MY_BK2"))) uint32 plc_pc_xor=0x0^0xAAAAAAAA;    //异或码

当需要写这个变量时,这三个位置都要更新;读取变量时,读取三个值做判断,取至少有两个相同的那个值。

为什么选取异或码而不是补码?这是因为MDK的整数是按照补码存储的,正数的补码与原码相同,在这种情况下,原码和补码是一致的,不但起不到冗余作用,反而对可靠性有害。比如存储的一个非零整数区因为干扰,RAM都被清零,由于原码和补码一致,按照3取2的“表决法”,会将干扰值0当做正确的数据。

8 对非易失性存储器进行备份存****储

非易失性存储器包括但不限于Flash、EEPROM、铁电。仅仅将写入非易失性存储器中的数据再读出校验是不够的。强干扰情况下可能导致非易失性存储器内的数据错误,在写非易失性存储器的期间系统掉电将导致数据丢失,因干扰导致程序跑飞到写非易失性存储器函数中,将导致数据存储紊乱。相关文章:EEPROM和Flash这样讲,我早就懂了。

一种可靠的办法是将非易失性存储器分成多个区,每个数据都将按照不同的形式写入到这些分区中,需要进行读取时,同时读出多份数据并进行表决,取相同数目较多的那个值。

9 软件****锁

对于初始化序列或者有一定先后顺序的函数调用,为了保证调用顺序或者确保每个函数都被调用,我们可以使用环环相扣,实质上这也是一种软件锁。此外对于一些安全关键代码语句(是语句,而不是函数),可以给它们设置软件锁,只有持有特定钥匙的,才可以访问这些关键代码。也可以通俗的理解为,关键安全代码不能按照单一条件执行,要额外的多设置一个标志。

比如,向Flash写一个数据,我们会判断数据是否合法、写入的地址是否合法,计算要写入的扇区。之后调用写Flash子程序,在这个子程序中,判断扇区地址是否合法、数据长度是否合法,之后就要将数据写入Flash。

由于写Flash语句是安全关键代码,所以程序给这些语句上锁:必须具有正确的钥匙才可以写Flash。这样即使是程序跑飞到写Flash子程序,也能大大降低误写的风险。

 /***************************************************************
 * 名称:RamToFlash() 
 * 功能:复制RAM的数据到FLASH,命令代码51。
 * 入口参数:dst        目标地址,即FLASH起始地址。以512字节为分界 
 *           src        源地址,即RAM地址。地址必须字对齐 
 *           no         复制字节个数,为512/1024/4096/8192 
 *           ProgStart  软件锁标志    
 * 出口参数:IAP返回值(paramout缓冲区) CMD_SUCCESS,SRC_ADDR_ERROR,DST_ADDR_ERROR, 
 SRC_ADDR_NOT_MAPPED,DST_ADDR_NOT_MAPPED,COUNT_ERROR,BUSY,未选择扇区 
 ****************************************************************/  
 void  RamToFlash(uint32 dst, uint32 src, uint32 no,uint8 ProgStart)  
 {
     PLC_ASSERT("Sector number",(dst>=0x00040000)&&(dst<=0x0007FFFF));
     PLC_ASSERT("Copy bytes number is 512",(no==512));
     PLC_ASSERT("ProgStart==0xA5",(ProgStart==0xA5));
     paramin[0] = IAP_RAMTOFLASH;       // 设置命令字  
     paramin[1] = dst;                  // 设置参数  
     paramin[2] = src;
     paramin[3] = no;
     paramin[4] = Fcclk/1000;
     if(ProgStart==0xA5)                //只有软件锁标志正确时,才执行关键代码  
     {
         iap_entry(paramin, paramout);  // 调用IAP服务程序                 
         ProgStart=0;
     }
     else  
     {
         paramout[0]=PROG_UNSTART;
     }
 }

该程序段是编程lpc1778内部Flash,其中调用IAP程序的函数iap_entry(paramin, paramout)是关键安全代码,所以在执行该代码前,先判断一个特定设置的安全锁标志ProgStart,只有这个标志符合设定值,才会执行编程Flash操作。如果因为意外程序跑飞到该函数,由于ProgStart标志不正确,是不会对Flash进行编程的。

1****0 通信

通讯线上的数据误码相对严重,通讯线越长,所处的环境越恶劣,误码会越严重。抛开硬件和环境的作用,我们的软件应能识别错误的通讯数据。对此有一些应用措施:

  • 制定协议时,限制每帧的字节数;

    每帧字节数越多,发生误码的可能性就越大,无效的数据也会越多。对此以太网规定每帧数据不大于1500字节,高可靠性的CAN收发器规定每帧数据不得多于8字节,对于RS485,基于RS485链路应用最广泛的Modbus协议一帧数据规定不超过256字节。因此,建议制定内部通讯协议时,使用RS485时规定每帧数据不超过256字节;

  • 使用多种校验

    编写程序时应使能奇偶校验,每帧超过16字节的应用,建议至少编写CRC16校验程序。

  • 增加额外判断

    1)增加缓冲区溢出判断。这是因为数据接收多是在中断中完成,编译器检测不出缓冲区是否溢出,需要手动检查,在上文介绍数据溢出一节中已经详细说明。

    2)增加超时判断。当一帧数据接收到一半,长时间接收不到剩余数据,则认为这帧数据无效,重新开始接收。可选,跟不同的协议有关,但缓冲区溢出判断必须实现。这是因为对于需要帧头判断的协议,上位机可能发送完帧头后突然断电,重启后上位机是从新的帧开始发送的,但是下位机已经接收到了上次未发送完的帧头,所以上位机的这次帧头会被下位机当成正常数据接收。这有可能造成数据长度字段为一个很大的值,填满该长度的缓冲区需要相当多的数据(比如一帧可能1000字节),影响响应时间;另一方面,如果程序没有缓冲区溢出判断,那么缓冲区很可能溢出,后果是灾难性的。

  • 重传机制

    如果检测到通讯数据发生了错误,则要有重传机制重新发送出错的帧。

1****1 开关量输入的检测、确认

开关量容易受到尖脉冲干扰,如果不进行滤除,可能会造成误动作。一般情况下,需要对开关量输入信号进行多次采样,并进行逻辑判断直到确认信号无误为止。

1****2 开关量输出

开关信号简单的一次输出是不安全的,干扰信号可能会翻转开关量输出的状态。采取重复刷新输出可以有效防止电平的翻转。

1****3 初始化信息的保存和恢复

微处理器的寄存器值也可能会因外界干扰而改变,外设初始化值需要在寄存器中长期保存,最容易被破坏。由于Flash中的数据相对不易被破坏,可以将初始化信息预先写入Flash,待程序空闲时比较与初始化相关的寄存器值是否被更改,如果发现非法更改则使用Flash中的值进行恢复。

公司目前使用的4.3寸LCD显示屏抗干扰能力一般。如果显示屏与控制器之间的排线距离过长或者对使用该显示屏的设备打静电或者脉冲群,显示屏有可能会花屏或者白屏。

对此,我们可以将初始化显示屏的数据保存在Flash中,程序运行后,每隔一段时间从显示屏的寄存器读出当前值和Flash存储的值相比较,如果发现两者不同,则重新初始化显示屏。下面给出校验源码,仅供参考。

定义数据结构:

图片

定义const修饰的结构体变量,存储LCD部分寄存器的初始值,这个初始值跟具体的应用初始化有关,不一定是表中的数据,通常情况下,这个结构体变量被存储到Flash中。

 /*LCD部分寄存器设置值列表*/  
 lcd_redu_list_struct const lcd_redu_list_str[]=
 {
   {SSD1963_Get_Address_Mode,{0x20}                                   ,1}, /*1*/ 
   {SSD1963_Get_Pll_Mn      ,{0x3b,0x02,0x04}                         ,3}, /*2*/ 
   {SSD1963_Get_Pll_Status  ,{0x04}                                   ,1}, /*3*/ 
   {SSD1963_Get_Lcd_Mode    ,{0x24,0x20,0x01,0xdf,0x01,0x0f,0x00}     ,7}, /*4*/ 
   {SSD1963_Get_Hori_Period ,{0x02,0x0c,0x00,0x2a,0x07,0x00,0x00,0x00},8}, /*5*/ 
   {SSD1963_Get_Vert_Period ,{0x01,0x1d,0x00,0x0b,0x09,0x00,0x00}     ,7}, /*6*/ 
   {SSD1963_Get_Power_Mode  ,{0x1c}                                   ,1}, /*7*/ 
   {SSD1963_Get_Display_Mode,{0x03}                                   ,1}, /*8*/ 
   {SSD1963_Get_Gpio_Conf   ,{0x0F,0x01}                              ,2}, /*9*/ 
   {SSD1963_Get_Lshift_Freq ,{0x00,0xb8}                              ,2}, /*10*/
 };

实现函数如下所示,函数会遍历结构体变量中的每一个命令,以及每一个命令下的初始值,如果有一个不正确,则跳出循环,执行重新初始化和恢复措施。这个函数中的MY_DEBUGF宏是我自己的调试函数,使用串口打印调试信息,在接下来的第五部分将详细叙述。

通过这个函数,我可以长时间监控显示屏的哪些命令、哪些位容易被干扰。程序里使用了一个被妖魔化的关键字:goto。大多数C语言书籍对goto关键字谈之色变,但你应该有自己的判断。在函数内部跳出多重循环,除了goto关键字,又有哪种方法能如此简洁高效!

 /** 
 * lcd 显示冗余 
 * 每隔一段时间调用该程序一次 
 */  
 void lcd_redu(void)  
 {
     uint8_t  tmp[8];
     uint32_t i,j;
     uint32_t lcd_init_flag;
     lcd_init_flag =0;
     for(i=0;i<sizeof(lcd_redu_list_str)/sizeof(lcd_redu_list_str[0]);i+)
     {
LCD_SendCommand(lcd_redu_list_str[i].lcd_command);
uyDelay(10);
for(j=0;j<lcd_redu_list_str[i].lcd_value_num;j++)
         {
tmp[j]=LCD_ReadData();
if(tmp[j]!=lcd_redu_list_str[i].lcd_get_value[j])
             {
lcd_init_flag=0x55;
MY_DEBUGF(MENU_DEBUG,("读lcd寄存器值与预期不符,命令为:0x%x,%d个参数,
             该参数正确值为:0x%x,实际读出值为:0x%x\n",lcd_redu_list_str[i].lcd_command,j+1,
lcd_redu_list_str[i].lcd_get_value[j],tmp[j]));
goto handle_lcd_init;
             }
         }
     }
handle_lcd_init:
if(lcd_init_flag==0x55)
     {
         //重新初始化LCD
         //一些必要的恢复措施  
     }
 }

1****4 陷阱

对于8051内核单片机,由于没有相应的硬件支持,可以用纯软件设置软件陷阱,用来拦截一些程序跑飞。对于ARM7或者Cortex-M系列单片机,硬件已经内建了多种异常,软件需要根据硬件异常来编写陷阱程序,用来快速定位甚至恢复错误。

1****5 阻塞处理

有时候程序员会使用while(!flag);语句阻塞在此等待标志flag改变,比如串口发送时用来等待一字节数据发送完成。这样的代码时存在风险的,如果因为某些原因标志位一直不改变则会造成系统死机。

一个良好冗余的程序是设置一个超时定时器,超过一定时间后,强制程序退出while循环。

2003年8月11日发生的W32.Blaster.Worm蠕虫事件导致全球经济损失高达5亿美元,这个漏洞是利用了Windows分布式组件对象模型的远程过程调用接口中的一个逻辑缺陷:在调用GetMachineName()函数时,循环只设置了一个不充分的结束条件。

原代码简化如下所示:

图片

微软发布的安全补丁MS03-026解决了这个问题,为GetMachineName()函数设置了充分终止条件。一个解决代码简化如下所示(并非微软补丁代码):

 HRESULT GetMachineName( WCHAR *pwszPath,  
 WCHARwszMachineName[MAX_COMPUTTERNAME_LENGTH_FQDN+1])
 {
        WCHAR *pwszServerName = wszMachineName;
        WCHAR *pwszTemp = pwszPath + 2;
        WCHAR *end_addr = pwszServerName +MAX_COMPUTTERNAME_LENGTH_FQDN;
        while (*pwszTemp != L’\\’ ) && (*pwszTemp != L’\0)&& (pwszServerName<end_addr))  /*充分终止条件*/  
              *pwszServerName++= *pwszTemp++;
        /*… */  
 }
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

嵌入式开发中的防御性C语言编程 的相关文章

  • 真正的模块化编程原来是这样的!

    已剪辑自 https mp weixin qq com s uo4tnsEnpULAruayZHcKAw 随着我们工程化经验的增加 xff0c 不知不觉的我们就会关心到这个问题 xff0c 模块化 xff0c 模块设计就显现出来 xff0c
  • 分享嵌入式软件调试方法和几个工具

    已剪辑自 https mp weixin qq com s dbYmBOISjd7tzniVT2l eg 我们常常说 xff0c 软件三分写七分调 实际开发中 xff0c 确实也是这样子的 我工作这几年了 xff0c 对这体会也越来越深 每
  • Ubuntu安装指定版本clang-format

    执行以下命令即可 xff1a wget O https apt llvm org llvm snapshot gpg key sudo apt key add sudo vim etc apt sources list 插入从https a
  • 一种简洁、可拓展的RTOS任务初始化设计

    已剪辑自 https mp weixin qq com s 9IN3AZsqnvgvYLukqvlEPQ 随着写代码功力的提升 xff0c 个人对于代码的整洁 优雅 可维护 易拓展等就有了一定的要求 xff0c 虽然自己曾经就属于那种全局变
  • 谁在滋养你,谁在消耗你

    我微信里有4000多个朋友 xff0c 或者准确来说应该是4000多个联系人 但其中只有极少数人的朋友圈给我留下非常深刻的印象 xff0c 这些人无疑是在滋养我 虽然人数我没有具体统计 xff0c 但是整体而言 xff0c 所占比例应该在1
  • 代码插桩技术

    百度百科 程序插桩 xff0c 最早是由J C Huang 教授提出的 xff0c 它是在保证被测程序原有逻辑完整性的基础上在程序中插入一些探针 xff08 又称为 探测仪 xff0c 本质上就是进行信息采集的代码段 xff0c 可以是赋值
  • 全数字仿真测试平台V-Sim TP

    已剪辑自 http www softtest cn show 37 html 全数字仿真测试平台V Sim TP 产品概述 V SimTP虚拟仿真测试平台是一套可对嵌入式系统进行虚拟仿真测试 快速原型验证的自动化测试平台 xff0c 适用于
  • 消息队列这么多,用哪个哟?

    已剪辑自 https mp weixin qq com s GJaZ00T2Ee5z3hF639XJnQ 提到消息队列 xff0c 大家一定不陌生 xff0c 无论是在校 面试还是工作 xff0c 我们涉及得都很多 有需求就有供应 xff0
  • 嵌入式软件分层隔离的典范

    已剪辑自 https mp weixin qq com s 9gVBZL0sTYIIcvQ bKn8gw 引言 xff1a 嵌入式软件开发分层 模块化是理想状态 xff0c 实际开发中因各种限制而有所取舍 xff0c 但这不妨碍学习参考优秀
  • 单片机ADC常见的几种滤波方法

    已剪辑自 https mp weixin qq com s ObtCPcxnBmpr3mR7NPkB7Q 如今传感器的种类越来越多 xff0c 数量也越来越多 xff0c 而这些传感器很多都会用到模拟量 xff0c 模拟量就离不开ADC 然
  • 浅谈民机软件适航宝典-DO-178

    已剪辑自 https mp weixin qq com s cyx9fSwpX35nDBkHqtO9lQ 序言 DO 178有一个不起眼的标题 机载系统和设备合格审定中的软件考虑 xff0c 但最好不要光看表面 实际上 xff0c 在业界中
  • 嵌入式状态机的几种骚操作

    已剪辑自 https mp weixin qq com s tulMJ7S7oOqV01J2E 9Xg 1 状态机基本术语 现态 xff1a 是指当前所处的状态 条件 xff1a 又称为 事件 xff0c 当一个条件被满足 xff0c 将会
  • 玩RTOS这么久,一问原子操作,蒙了~

    已剪辑自 https mp weixin qq com s kvxcOHT xHtMAjQqJu7Y2g 外链图片转存失败 源站可能有防盗链机制 建议将图片保存下来直接上传 img C3f9Rrei 1668695258073 https
  • 瑞利信道:从原理到实现

    瑞利信道模型 瑞利信道模型是无线通信信道最重要 最基础的的仿真模型 无线信道中的平坦衰落信道基本上都是在瑞利信道模型的基础上修改而成 xff0c 比如应用同样广泛的莱斯信道就可以通过在瑞利信道的基础上简单的添加直流分量实现 xff0c 而频
  • 分享一个通用的嵌入式驱动层

    https mp weixin qq com s bzPg5SremHDeIiguzvUVFA
  • 这次把怎么做好一个PPT讲清-总体篇

    文章目录 一 背景二 图表化 图示化三 关键词设计四 版式层级五 逻辑关系图 1 xff09 常用逻辑 2 xff09 如何让逻辑关系图好看 六 对齐 分组和对比 对齐 分组 分组就是将同类得信息放在一起 xff0c 靠的更近一点 那么 x
  • 这次把怎么做好一个PPT讲清-画图篇

    文章目录 概述布尔运算PPT幻灯片中如何设置形状对象格式每一个图形既是一个形状 xff0c 又是一个文本框 如何用PPT来实现三维3D效果 xff0c 附参数设置详解怎么用ppt画三维立体图 PPT做3D可动样机 PPT做3D 动态图标 P
  • 这次把怎么做好一个PPT讲清-其他技巧篇

    文章目录 如何统一批量设置PPT的中文字体和英文字体ppt如何插入页码和时间工具 原料插入页码和时间的步骤 xff1a 注意事项 在Powerpoint幻灯片里显示总页数在Powerpoint 2010中添加幻灯片编号在Powerpoint
  • 这次把怎么做好一个PPT讲清-审美篇

    要提高审美 xff0c 主要是靠不断的看优秀的作品来知道什么是美的 xff0c 这个短时间很难速成 xff0c 只能靠不断的积累 如何做出具有高级感的PPT xff1f 已剪辑自 https zhuanlan zhihu com p 386
  • 这样做时间轴,让你的PPT更出彩!

    文章目录 方法一 xff1a 美化时间节点 方法二 xff1a 利用图片中的 轴 方法三 xff1a 时间轴不一定需要 轴 方法四 xff1a 把时间轴拆成数页 总结 已剪辑自 https zhuanlan zhihu com p 5667

随机推荐

  • PPT文字很多的排版,PPT图片很多的排版,PPT图文排版

    文章目录 专业设计师是如何把一个word变成PPT的 xff1f 搭建 骨架 xff0c 填充 血肉 内页的排版 对页面的可视化处理 PPT文字巨多 xff01 领导还不让删 xff0c 怎么排版才高大上 xff1f 排版技巧一 xff1a
  • PPT目录页怎么设计才高大上?告诉你一个万能排版法!

    已剪辑自 https zhuanlan zhihu com p 64526891 嗨 xff0c 各位木友们好呀 xff0c 我是小木 帅的人都知道 xff0c 一个完整的PPT xff0c 一般应该要有 封面 43 目录 43 过渡页 4
  • 这样做框架结构图,让你的PPT更有创意!

    已剪辑自 https zhuanlan zhihu com p 58834710 嗨 xff0c 各位木友们好呀 xff0c 我是小木 昨天 xff0c 有个跟我一样鸟人的鸟人让我帮忙做个框架结构图 xff1a 可惜当时我不在办公室 xff
  • 如何画架构图?

    平时做过一些系统设计 xff0c 也写过一些系统分析文章 xff0c 从组件 关系 交互等方面提供一些建议 xff0c 并用我之前写文章画的一些图举些例子 构成系统的组件通过形状 颜色 名称来逼近其概念 LevelDB 主要构件如上面 Le
  • 主定理的证明及应用举例

    主定理 主定理最早出现在 算法导论 中 xff0c 提供了分治方法带来的递归表达式的渐近复杂度分析 规模为n的问题通过分治 xff0c 得到a个规模为n b的问题 xff0c 每次递归带来的额外计算为c n d T n lt 61 aT n
  • 程序员怎样才能写出一篇好的博客或者技术文章?

    文章目录 来分享下鹅厂多位技术同学关于如何写好技术文章的经验 1 为什么要写文章 1 1 对作者的好处 1 1 1 复盘学习成果 xff0c 巩固知识理解 1 1 2 提升思考能力 1 1 3 传播技术知识 xff0c 积累技术资产 1 1
  • 数字孪生技术有没有真正的实用价值?

    作为一个数字孪生领域的技术公司负责人 xff0c 我尽可能用比较直白的话来描述一下我对数字孪生行业以及数字孪生价值的理解 纵观数字孪生相关的公司 xff0c 主要有两个流派 xff0c 一派是具有互联网基因的数字孪生创业公司 xff0c 一
  • 你在编程过程中养成了哪些好习惯?

    写工作日志 我一直有大量写笔记的习惯 编程的时候 xff0c 也经常遇到一些麻烦的问题 xff0c 思路转瞬即逝 xff0c 于是把所有这些思路记录下来 xff0c 会在以后的搜索中成为重要的灵感来源 我的工作日志里通常以项目为单位 xff
  • 如何让 PPT 中的表格更美观?

    这个不难 xff0c 不信你看 做PPT表格 xff0c 千万不要直接把Excel截个图粘贴到PPT里 或者网上找到相应的表格图片 xff0c 也直接粘贴到PPT中 这样做的PPT表格肯定不好看呀 今天和大家分享几个表格美化的小技巧 xff
  • 这次把怎么做好一个PPT讲清-演讲篇

    商务演讲与汇报 一 目标 xff1a 演讲必须有清晰的目标 演讲 xff1a 影响他人发生积极的 改变 注意 xff0c 目标就要设定的影响听众在听完你的演讲后发生积极的改变 xff1b 例 xff1a 5月初向领导做月度工作汇报 让领导在
  • 这次把怎么做好一个PPT讲清-动画篇

    干货预警 xff01 作为一位PPT发烧友看过诸多PPT案例 xff0c 分享几个高大上的动画效果 文末有福利 xff01 废话不多说 xff0c 直接上重点 xff0c 本文主要讲八个动画技巧 xff0c 我们来看先目录 xff1a 收藏
  • 重新认知发明,全网保姆级入门说明

    已剪辑自 https mp weixin qq com s IDQXYXpWQlaW1NyX36H2vQ 关注 林外的日课 公众号 xff1a 每日思考 xff0c 每周更新 发明 xff0c 是指对产品 方法或者其改进所提出的新的技术方案
  • 软件测试需要掌握哪些技术?

    文章目录 1 黑盒测试 白盒测试 灰盒测试1 1 黑盒测试1 2 白盒测试1 3 灰盒测试 2 自顶向下集成和自底向上集成各自的优缺点 2 1 自顶向下集成2 2 自底向上集成 3 按照开发阶段划分 xff0c 软件测试可以分为哪几个流程
  • 画时序图的四个好用的工具~

    已剪辑自 https mp weixin qq com s xvCOLaGARp15vCRq6w8h2Q 分享几个画时序图的软件 xff0c 一些通信协议 xff0c 如I2C SPI UART MIPI等 xff0c 都会涉及到时序 Ti
  • Google软件工程:什么是软件工程

    文章目录 编程 软件工程软件工程的3个特性时间与变化海勒姆定律 xff08 Hyrum s Law xff09 目标不是 没有变化 规模和效率左移思维 权衡和成本最后 已剪辑自 https mp weixin qq com s GhYfH3
  • 多普勒失真信号重采样的Matlab仿真分析

    多普勒失真信号重采样的Matlab仿真分析 应用场景 水声通信指的是使用声信号在水中传输数据 相对而言 xff0c 电磁信号在水中吸收严重衰减过快 xff0c 光信号受水中悬浮颗粒的影响 xff0c 也无法完成远距离传输 这两种信号的传播距
  • 嵌入式为何钟爱SourceInsight,主要因为这个功能~

    已剪辑自 https mp weixin qq com s F gafwbZswpnY8EaCz8HxQ 不管是玩单片机还是嵌入式linux xff0c 只要是与硬件结合比较紧密的部分目前基本上还是C语言 xff0c 当然了 xff0c 不
  • 华科师兄最近的六条感悟

    已剪辑自 https mp weixin qq com s P6vz2zDTnCNli0GdyKX98Q 孤独之前是迷茫 孤独之后便是成长 曾经我是一个无法承受孤独的人 xff0c 无法和自己独处 xff0c 喜欢喧嚣的感觉 慢慢我发现这种
  • 一个优秀的程序员应该养成哪些好的习惯?

    文章目录 一 写代码前先想好思路 xff0c 先规划框架 xff0c 再到局部实现二 注重代码风格三 注重代码执行效率四 掌握一些编码原则五 解决问题时 xff0c 对于原理性的问题 xff0c 不要面向搜索引擎编程 六 注重基础知识的学习
  • 嵌入式开发中的防御性C语言编程

    嵌入式产品的可靠性自然与硬件密不可分 xff0c 但在硬件确定 并且没有第三方测试的前提下 xff0c 使用防御性编程思想写出的代码 xff0c 往往具有更高的稳定性 防御性编程首先需要认清C语言的种种缺陷和陷阱 xff0c C语言对于运行