• Valgrind手册
  • Quick start

文章目录

  • 概述
    • 体系结构
    • Valgrind 原理
    • 安装
    • 编译时需要注意
    • 快速入门
    • LEAK SUMMARY:内存泄漏总结(分类)
  • 实践
    • 第一个例子:没有内存泄漏
    • 第二个例子:只申请内存而不释放
      • 编译程序1
      • 编译程序2
    • 第3个例子: 使用未初始化的内存
    • 第3个例子: 内存读写越界
    • 第4个例子: 重复释放
    • 第4个例子: malloc与delete释放问题
    • 第5个例子:内存覆盖

概述

体系结构

Valgrind 是一套linux下,开放源代码的仿真调试工具的集合。Valgrind有内核(core)以及基于内核的其他调试工具组成。内核类似于一个框架,它模拟了一个CPU环境,并提供服务给其他工具;而其他工具则类似于插件,利用内核提供的服务完成各种特定的内存调试任务。


Valgrind 包括如下一些工具:

  • Memcheck。这是 valgrind 应用最广泛的工具,一个重量级的内存检查器,能够发现开发中绝大多数内存错误使用情况,比如:使用未初始化的内存,使用已经释放了的内存,内存访问越界等
  • Callgrind。它主要用来检查程序中函数调用过程中出现的问题。
  • Cachegrind。它主要用来检查程序中缓存使用出现的问题。
  • Helgrind。它主要用来检查多线程程序中出现的竞争问题。
  • Massif。它主要用来检查程序中堆栈使用中出现的问题。
  • Extension。可以利用 core 提供的功能,自己编写特定的内存调试工具。

Valgrind 原理

valgrind是一个提供了一些debug和优化工具的工具箱,可以使得你的程序减少内存泄漏或者错误访问.

valgrind 默认使用 memcheck 去检查内存问题.

memcheck 检测内存问题的原理如下图所示:

Memcheck 能够检测出内存问题,关键在于其建立了两个全局表。

  • valid-value map:
    对于进程的整个地址空间中的每一个字节(byte),都有与之对应的 8 个 bits;对于 CPU 的每个寄存器,也有一个与之对应的 bit 向量。这些 bits 负责记录该字节或者寄存器值是否具有有效的、已初始化的值。
  • valid-address map
    对于进程整个地址空间中的每一个字节(byte),还有与之对应的 1 个 bit,负责记录该地址是否能够被读写。

检测原理:

  • 当要读写内存中某个字节时,首先检查 valid-address map 中这个字节对应的 A bit。如果该A bit显示该位置是无效位置,memcheck 则报告读写错误。
  • 内核(core)类似于一个虚拟的 CPU 环境,这样当内存中的某个字节被加载到真实的 CPU 中时,该字节对应的 V bit (在 valid-value map 中) 也被加载到虚拟的 CPU 环境中。一旦寄存器中的值,被用来产生内存地址,或者该值能够影响程序输出,则 memcheck 会检查对应的 V bits,如果该值尚未初始化,则会报告使用未初始化内存错误。

安装

centos下:

sudo yum install valgrind

源代码方式安装:

wget https://fossies.org/linux/misc/valgrind-3.15.0.tar.bz2
tar -jxvf valgrind-3.15.0.tar.bz2
cd valgrind-3.15.0
./configure
make
sudo make install

编译时需要注意

1、为了使 valgrind 发现的错误更精确,如能够定位到源代码行,建议在编译时加上-g
参数,编译优化选项请选择 O0,虽然这会降低程序的执行效率。

对于CMakelist

add_definitions("-Wall -g")

2、对于C++程序,请启动-fno-inline忽略代码中的 inline 关键字,该选项使编译器将内联函数以普通函数对待;等同无优化选项时的处理)。这样可以更轻松地查看函数调用链。

或者,Valgrind选项 --read-inline-info=yes指示Valgrind读取描述内联信息的调试信息

快速入门

使用valgrind 很简单, 首先编译好要测试的程序 (为了使valgrind发现的错误更精确,如能够定位到源代码行,建议在编译时加上-g参数,编译优化选项请选择O0,虽然这会降低程序的执行效率。), 假设运行这个程序的命令是

./a.out arg1 arg2

那么要使用 valgrind 的话只需要运行

valgrind --leak-check=yes ./a.out arg1 arg2

LEAK SUMMARY:内存泄漏总结(分类)

definitely lost: 4 bytes in 1 blocks:绝对丢失,这种情况应该由程序员来解决,下面几种情况,可以当作参考

  • indirectly lost: 0 bytes in 0 blocks:间接丢失
  • possibly lost: 0 bytes in 0 blocks:可能丢失
  • still reachable: 0 bytes in 0 blocks:仍然可以访问
  • suppressed: 0 bytes in 0 blocks:抑制错误中的丢失

实践

第一个例子:没有内存泄漏

#include <iostream.h>
int main()
{cout << "Hello kiccleaf!/n" << endl;return 0;
}

编译

gcc -o ./Share ./main.cpp

运行:

valgrind --tool=memcheck --leak-check=full ./Share

结果:

==56806== Memcheck, a memory error detector
==56806== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==56806== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==56806== Command: ./Share
==56806==
Hello kiccleaf!/n
==56806==
==56806== HEAP SUMMARY:
==56806==     in use at exit: 0 bytes in 0 blocks
==56806==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==56806==
==56806== All heap blocks were freed -- no leaks are possible
==56806==
==56806== For lists of detected and suppressed errors, rerun with: -s
==56806== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

第二个例子:只申请内存而不释放

#include <stdlib.h>
#include <stdio.h>
void func()
{//只申请内存而不释放void *p=malloc(sizeof(int));
}
int main()
{func();getchar();return 0;
}

编译程序1

gcc -o ./a.out ./main.cpp

使用valgrind命令来执行程序同时输出日志到文件

valgrind --log-file=valReport --leak-check=full --show-reachable=yes --leak-resolution=low ./a.out
  • –log-file=valReport 是指定生成分析日志文件到当前执行目录中,文件名为valReport
  • –leak-check=full 显示每个泄露的详细信息
  • –show-reachable=yes 是否检测控制范围之外的泄漏,比如全局指针、static指针等,显示所有的内存泄露类型
  • –leak-resolution=low 内存泄漏报告合并等级

最后执行输出的内容如下

报告解读,其中54017是指进程号,如果程序使用了多进程的方式来执行,那么就会显示多个进程的内容

第一段是valgrind的基本信息

==54017== Memcheck, a memory error detector
==54017== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==54017== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==54017== Command: ./a.out
==54017== Parent PID: 52130

第二段是对堆内存分配的总结信息,其中提到程序一共申请了1次内存,其中0次释放了,4 bytes被分配

==54017== HEAP SUMMARY:
==54017==     in use at exit: 4 bytes in 1 blocks
==54017==   total heap usage: 1 allocs, 0 frees, 4 bytes allocated

第三段的内容描述了内存泄露的具体信息,其中有一块内存占用4字节,在调用malloc分配,调用栈中可以看到是func函数最后调用了malloc,所以这一个信息是比较准确的定位了我们泄露的内存是在哪里申请的

==54017== 4 bytes in 1 blocks are definitely lost in loss record 1 of 1
==54017==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==54017==    by 0x40057E: func() (in /home/oceanstar/CLionProjects/Share/src/a.out)
==54017==    by 0x40058D: main (in /home/oceanstar/CLionProjects/Share/src/a.out)

最后这一段是总结,4字节为一块的内存泄露

==54017== LEAK SUMMARY:
==54017==    definitely lost: 4 bytes in 1 blocks
==54017==    indirectly lost: 0 bytes in 0 blocks
==54017==      possibly lost: 0 bytes in 0 blocks
==54017==    still reachable: 0 bytes in 0 blocks
==54017==         suppressed: 0 bytes in 0 blocks

编译程序2

 gcc -g -o ./a.out ./main.cpp

运用valgrind检测内存泄漏:

==55430== Memcheck, a memory error detector
==55430== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==55430== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==55430== Command: ./a.out
==55430==
^C==55430==
==55430== Process terminating with default action of signal 2 (SIGINT)
==55430==    at 0x4F25F70: __read_nocancel (in /usr/lib64/libc-2.17.so)
==55430==    by 0x4EB2B13: _IO_file_underflow@@GLIBC_2.2.5 (in /usr/lib64/libc-2.17.so)
==55430==    by 0x4EB3CE1: _IO_default_uflow (in /usr/lib64/libc-2.17.so)
==55430==    by 0x4EAE6B9: getchar (in /usr/lib64/libc-2.17.so)
==55430==    by 0x400592: main (main.cpp:11)
==55430==
==55430== HEAP SUMMARY:
==55430==     in use at exit: 4 bytes in 1 blocks
==55430==   total heap usage: 1 allocs, 0 frees, 4 bytes allocated
==55430==
==55430== 4 bytes in 1 blocks are definitely lost in loss record 1 of 1
==55430==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==55430==    by 0x40057E: func() (main.cpp:6)
==55430==    by 0x40058D: main (main.cpp:10)
==55430==
==55430== LEAK SUMMARY:
==55430==    definitely lost: 4 bytes in 1 blocks
==55430==    indirectly lost: 0 bytes in 0 blocks
==55430==      possibly lost: 0 bytes in 0 blocks
==55430==    still reachable: 0 bytes in 0 blocks
==55430==         suppressed: 0 bytes in 0 blocks
==55430==
==55430== For lists of detected and suppressed errors, rerun with: -s
==55430== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

可以看到,在head summary中,有该程序使用的总heap内存量,分配内存次数和释放内存次数,如果分配内存次数和释放内存次数不一致则说明有内存泄漏。

下面会有具体的分析:包括在main函数中和f函数中通过malloc申请了内存但是没有释放造成了多大的内存损失都标记有,–甚至行号都标记了,debug神器!

变形1:

#include <stdio.h>
#include <iostream>int main()
{int *x;x = static_cast<int *>(malloc(8 * sizeof(int)));x = static_cast<int *>(malloc(8 * sizeof(int)));return 0;
}

报告是:

==59320== HEAP SUMMARY:
==59320==     in use at exit: 64 bytes in 2 blocks
==59320==   total heap usage: 2 allocs, 0 frees, 64 bytes allocated
==59320==        *********有几个没有释放就会有几个definitely lost*************
==59320== 32 bytes in 1 blocks are definitely lost in loss record 1 of 2
==59320==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==59320==    by 0x400691: main (main.cpp:7)
==59320==
==59320== 32 bytes in 1 blocks are definitely lost in loss record 2 of 2
==59320==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==59320==    by 0x4006A2: main (main.cpp:8)
==59320==
==59320== LEAK SUMMARY:
==59320==    definitely lost: 64 bytes in 2 blocks
==59320==    indirectly lost: 0 bytes in 0 blocks
==59320==      possibly lost: 0 bytes in 0 blocks
==59320==    still reachable: 0 bytes in 0 blocks
==59320==         suppressed: 0 bytes in 0 blocks
==59320==
==59320== For lists of detected and suppressed errors, rerun with: -s
==59320== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

第3个例子: 使用未初始化的内存

#include <stdio.h>
int main()
{int x;if(x == 0){printf("X is zero");}return 0;
}
  • Conditional jump or move depends on uninitialised value(s)
$ valgrind --tool=memcheck --leak-check=full ./Share
==57052== Memcheck, a memory error detector
==57052== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==57052== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==57052== Command: ./Share
==57052==
==57052== Conditional jump or move depends on uninitialised value(s)
==57052==    at 0x400549: main (main.cpp:5)
==57052==
X is zero==57052==
==57052== HEAP SUMMARY:
==57052==     in use at exit: 0 bytes in 0 blocks
==57052==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==57052==
==57052== All heap blocks were freed -- no leaks are possible
==57052==
==57052== Use --track-origins=yes to see where uninitialised values come from
==57052== For lists of detected and suppressed errors, rerun with: -s
==57052== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

第3个例子: 内存读写越界

#include <stdio.h>
#include <iostream>int main()
{int len = 5;int *pt = (int*)malloc(len*sizeof(int)); //problem1: not freedint *p = pt;for (int i = 0; i < len; i++){p++;}*p = 5; //problem2: heap block overrunprintf("%d\n", *p); //problem3: heap block overrun// free(pt);return 0;
}

problem1: 指针pt申请了空间,但是没有释放;
problem2: pt申请了5个int的空间,p经过4次循环(i=3时)已达到最后申请的p[4], 在i=4时p所指向的空间没有申请过; (下面valgrind报告中 Invalid write of size 4)
problem1: 同line8 (下面valgrind报告中 Invalid read of size 4 )

==58261== Memcheck, a memory error detector
==58261== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==58261== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==58261== Command: ./Share
==58261==
==58261== Invalid write of size 4
==58261==    at 0x400707: main (main.cpp:12)
==58261==  Address 0x5a23054 is 0 bytes after a block of size 20 alloc'd
==58261==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==58261==    by 0x4006DC: main (main.cpp:7)
==58261==
==58261== Invalid read of size 4
==58261==    at 0x400711: main (main.cpp:13)
==58261==  Address 0x5a23054 is 0 bytes after a block of size 20 alloc'd
==58261==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==58261==    by 0x4006DC: main (main.cpp:7)
==58261==
5
==58261==   ****************这一段都是因为malloc但是没有free,如果free,就不会出现这个*******************
==58261== HEAP SUMMARY:
==58261==     in use at exit: 20 bytes in 1 blocks
==58261==   total heap usage: 1 allocs, 0 frees, 20 bytes allocated
==58261==
==58261== 20 bytes in 1 blocks are definitely lost in loss record 1 of 1
==58261==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==58261==    by 0x4006DC: main (main.cpp:7)
==58261==
==58261== LEAK SUMMARY:
==58261==    definitely lost: 20 bytes in 1 blocks
==58261==    indirectly lost: 0 bytes in 0 blocks
==58261==      possibly lost: 0 bytes in 0 blocks
==58261==    still reachable: 0 bytes in 0 blocks
==58261==         suppressed: 0 bytes in 0 blocks
==58261== ****************************************************************
==58261== For lists of detected and suppressed errors, rerun with: -s
==58261== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)
 //--------------------如果没有出现堆内存泄漏,会打印-----------------
==58522== HEAP SUMMARY:
==58522==     in use at exit: 0 bytes in 0 blocks
==58522==   total heap usage: x allocs, xfrees, xxxx bytes allocated
==58522==
==58522== All heap blocks were freed -- no leaks are possible

变形1:写越界

#include <stdio.h>
#include <iostream>int main()
{int *x;x = static_cast<int *>(malloc(8 * sizeof(int)));x[9] = 0; //数组下标越界   Invalid write of size 4free(x);return 0;
}

变形2:读越界

#include <stdio.h>
#include <iostream>int main()
{int *x;x = static_cast<int *>(malloc(8 * sizeof(int)));std::cout << x[9] ; //数组下标越界    Invalid read of size 4free(x);return 0;
}

第4个例子: 重复释放

#include <stdio.h>
#include <iostream>int main()
{int *x;x = static_cast<int *>(malloc(8 * sizeof(int)));x = static_cast<int *>(malloc(8 * sizeof(int)));free(x);free(x);return 0;
}

报告:

==59602== Invalid free() / delete / delete[] / realloc()
==59602==    at 0x4C2B06D: free (vg_replace_malloc.c:540)
==59602==    by 0x4006FE: main (main.cpp:10)
==59602==  Address 0x5a230a0 is 0 bytes inside a block of size 32 free'd
==59602==    at 0x4C2B06D: free (vg_replace_malloc.c:540)
==59602==    by 0x4006F2: main (main.cpp:9)
==59602==  Block was alloc'd at
==59602==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==59602==    by 0x4006E2: main (main.cpp:8)
==59602==
==59602==
==59602== HEAP SUMMARY:
==59602==     in use at exit: 32 bytes in 1 blocks
==59602==   total heap usage: 2 allocs, 2 frees, 64 bytes allocated
==59602==
==59602== 32 bytes in 1 blocks are definitely lost in loss record 1 of 1
==59602==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==59602==    by 0x4006D1: main (main.cpp:7)
==59602==
==59602== LEAK SUMMARY:
==59602==    definitely lost: 32 bytes in 1 blocks
==59602==    indirectly lost: 0 bytes in 0 blocks
==59602==      possibly lost: 0 bytes in 0 blocks
==59602==    still reachable: 0 bytes in 0 blocks
==59602==         suppressed: 0 bytes in 0 blocks
==59602==
==59602== For lists of detected and suppressed errors, rerun with: -s
==59602== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

第4个例子: malloc与delete释放问题

int main()
{char* str = (char*)malloc(5*sizeof(char));delete []str;
}

用malloc申请空间的指针用free释放;用new申请的空间用delete释放 (valgrind中Mismatched free() / delete / delete []);

==61950== Mismatched free() / delete / delete []
==61950==    at 0x4C2BB8F: operator delete[](void*) (vg_replace_malloc.c:651)
==61950==    by 0x4006E8: main (main.cpp:8)
==61950==  Address 0x5a23040 is 0 bytes inside a block of size 5 alloc'd
==61950==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==61950==    by 0x4006D1: main (main.cpp:7)
==61950==
==61950==
==61950== HEAP SUMMARY:
==61950==     in use at exit: 0 bytes in 0 blocks
==61950==   total heap usage: 1 allocs, 1 frees, 5 bytes allocated
==61950==
==61950== All heap blocks were freed -- no leaks are possible

第5个例子:内存覆盖

int main()
{char str[11];for (int i = 0; i < 11; i++){str[i] = i;}memcpy(str + 1, str, 5);char x[5] = "abcd";strncpy(x + 2, x, 3);
}

问题出在memcpy上, 将str指针位置开始copy 5个char到str+1所指空间,会造成内存覆盖。strncpy也是同理。

==61609== Source and destination overlap in memcpy(0x1ffefffe31, 0x1ffefffe30, 5)
==61609==    at 0x4C2E81D: memcpy@@GLIBC_2.14 (vg_replace_strmem.c:1035)
==61609==    by 0x400721: main (main.cpp:11)
==61609==
==61609== Source and destination overlap in strncpy(0x1ffefffe25, 0x1ffefffe23, 3)
==61609==    at 0x4C2D453: strncpy (vg_replace_strmem.c:552)
==61609==    by 0x400748: main (main.cpp:14)
==61609==
==61609==
==61609== HEAP SUMMARY:
==61609==     in use at exit: 0 bytes in 0 blocks
==61609==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==61609==
==61609== All heap blocks were freed -- no leaks are possible
#!/bin/sh
# $Id$
#
if [ -z "$1" ]; thenecho "Usage: `basename $0` prog"exit 1
fi
valgrind --tool=memcheck \
--leak-check=full \
--time-stamp=yes \
--show-reachable=yes \
--track-origins=yes \
--trace-children=no \
--leak-resolution=med \
--track-fds=yes \
--log-file=myvalmem.log \
"$@"
  • –track-origins=yes表示开启“使用未初始化的内存”的检测功能,并打开详细结果。如果没有这句话,默认也会做这方面的检测,但不会打印详细结果。

工具:valgrind学习相关推荐

  1. Python for虚幻引擎编辑器工具脚本学习教程

    Python for Unreal Engine Editor Tools Scripting MP4 |视频:h264,1280×720 |音频:AAC,44.1 KHz,2 Ch 语言:英语+中英 ...

  2. linux平台软件动态分析工具valgrind系列工具及其可视化

    linux平台软件动态分析工具valgrind系列工具 Memcheck–内存检查工具 Callgrind–函数调用分析工具 Cachegrind–缓存命中分析工具 Helgrind–线程分析工具 M ...

  3. Ubuntu下内存泄露检测工具Valgrind的使用

    在VS中可以用VLD检测是否有内存泄露,可以参考http://blog.csdn.net/fengbingchun/article/details/44195959,下面介绍下Ubuntu中内存泄露检 ...

  4. [译]通过使用Chrome的开发者工具来学习JavaScript

    原文:https://gist.github.com/4158604 本文作者是Peter Rybin,Chrome开发者工具团队成员. 本文中,我们将通过使用Chrome的开发者工具,来学习Java ...

  5. 工具的学习使用(二):快捷键、工具、批处理

    快捷键 Ctrl + Shift + A 核心快捷键 : 查看 Phpstorm 的 action,所谓 action 便是 Phpstorm 的一个原子操作,只要记住这些操作的关键字,进行搜索便可以 ...

  6. python 持续集成工具_持续集成工具: Jenkins学习

    持续集成工具: Jenkins学习 -- 部分内容收集自网络,如有侵权,请联系作者删除 一. 概念 在过去的开发整体流程中,是所有人写好代码之后统一进行合并(svn,git),然后进行测试,确保准发布 ...

  7. 【调试】Linux下超强内存检测工具Valgrind

    [调试]Linux下超强内存检测工具Valgrind 内容简介 Valgrind是什么? Valgrind的使用 Valgrind详细教程 1. Valgrind是什么? Valgrind是一套Lin ...

  8. 7.4 用学习工具提高学习的效率——《逆袭大学》连载

    返回到[全文目录] 目录 7.4 用学习工具提高学习的效率 用周计划安排好你的时间 番茄钟来帮忙 用思维导图用好我们的大脑 7.4 用学习工具提高学习的效率 古语云:"工欲善其事,必先利其器 ...

  9. C++ 内存泄漏检测工具valgrind简单使用

    C++ 内存泄漏检测工具valgrind简单使用 目录 C++ 内存泄漏检测工具valgrind简单使用 valgrind安装 valgrind测试内存泄漏 valgrind安装 通过软件商店下载: ...

  10. 元数据管理工具Atlas学习笔记之集成

    文章目录 背景 环境 Atlas安装 solr Atlas Atlas启动 启动Hadoop.ZooKeeper.HBase.Kafka.Hive和MySQL Hadoop 启动ZooKeeper 启 ...

最新文章

  1. centos 6 添加svn 的1.7版本yum源
  2. Html转义字符列表
  3. Markdown矩阵、表格和数学公式
  4. vgg11/13/16/19-pytorch实现
  5. Systemd 入门及常用命令
  6. Linux中shell模块的考试,linux下的shell编程要考试了题目这里有可是表示不会 求帮忙...
  7. 详细回复某个CSDN网友,对我的文章和技术实力以及CSDN的吐槽
  8. linux无后缀名程序运行,linux – 如何在Ubuntu上运行无扩展(也许是ELF)文件?
  9. Vue + Spring Boot 项目实战(八):导航栏与图书页面设计
  10. echarts 3d地图_用Echarts绘制地图-绘制省级地图
  11. search engine
  12. Eclipse代码自动提示设置
  13. 下载蓝盒插件_bilibili哔哩哔哩下载助手
  14. 同步助手 android 微信 表情包,微信表情轻松导,同步助手带你装逼带你飞
  15. Android Studio 打包Jar
  16. 绎维软件F-One获得B轮融资,华创资本领投,齐银基金跟投...
  17. php pdf文档内容修改,php2pdf-如何使用php修改pdf中的内容,并且保证格式不乱
  18. Havok和Physx对比
  19. Unrecognized option: --add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
  20. PHP环境安装到U盘,Windows To Go辅助工具一键把Win10系统安装到U盘 | 麦田一棵葱

热门文章

  1. 淘宝联盟pub平台又开始一轮新的升级
  2. 基于微信疫苗预约小程序毕业设计毕设作品(7)中期检查报告
  3. servlet基于JavaWeb实现疫情环境下校园宿舍寝室管理系统
  4. 2013汇总计算 广联达gcl_广联达钢筋算量GGJ2013快捷操作汇总
  5. 数据分析--对“数据分析”相关岗位的综合分析
  6. yolov3系列(二)-keras-yolo3训练自己的数据
  7. 写文章一年多以来,我经历了被喷被拉黑被赞美,我哭了
  8. 以给定值x为基准将单链表分割为两部分,所有小于x的结点都排在大于或等于x的结点之前。
  9. 供应链管理计算机二级,计算机二级考试真题-Word-林楚楠-供应链管理论文
  10. QQ靓号申请器v1.1.0.0【已更新】