一个简单的可移植 模块/系统动态内存 跟踪方法

一个简单的可移植  模块/系统动态内存 跟踪方法

 

 

在嵌入式系统或一些内存申请,释放频繁的场合,很难统计当前使用的动态内存,和整个软件模块运行过程中的内存使用的峰值;或者模块卸载时内存有无完全释放,还有就是动态内存的踩踏检查。针对这种需求,本文给出一种 简单的解决办法,并用 c code 描述出来。

 

 

感性认识

本文命名所建立的跟踪模块为mtrace

 

mtrace可以用来 跟踪当前 系统/模块的内存 使用情况 ,以及估算系统/模块的占用内存的极值 系统/模块 卸载时的 内存 泄露情况。

 

模块内存跟踪只在  dbg模式下有效 rom 下自动 变成mode 0

模块内存跟踪 0,1,2,3 四种模式 ,其中 mode 03 没有内存使用统计;mode 12有内存使用统计,具体说明如下。

 

 

Mode 总开关 FS_TRACE_MEM  控制

 

FS_TRACE_MEM =0 (mode 0) 其上所有 系统/模块 不使用内存跟踪

 

FS_TRACE_MEM =1 (mode 1)其上所有 系统/模块使用内存跟踪,只统计 系统/模块 占用总量大小,具体在各系统/模块 声明的TRACE_MEMSTAT 中,如fat fat_memstatntfs ntfs_memstathfs+hfsplus_memstat default_memstat 为其上所有 系统/模块 内存跟踪统计,如下图

说明:

       int calloc;   当前模块调用 calloc申请 的字节数

       int malloc;  当前模块调用 malloc申请 的字节数

       int realloc;  当前模块调用 realloc申请 的字节数

       int total;    当前模块迄今 申请内存 的总字节数 (以上3项总和)

       int max_once;  模块迄今 一次申请 最多字节数

       int max_total;  模块迄今 使用最多情况下的 总字节数,即峰值

 

       int calloc_cnt;  模块 调用 有效calloc的次数

       int malloc_cnt; 模块 调用 有效malloc的次数

       int realloc_cnt;  模块 调用 有效realloc的次数

       int total_cnt;    模块 申请内存 的总次数 (以上3项总和)

       int max_total_cnt; 模块迄今 申请内存 的总次数最大值

 

FS_TRACE_MEM =2 (mode 2)其上所有 系统/模块 使用内存跟踪,包括统计占用总量大小,内存分配日志 ,具体在各 系统/模块 声明的TRACE_MEMSTAT ;而且日志 极限数目 由每个系统/模块 MAX_ALLOC_ENTRYS 控制 。如fatfat_memstatntfs ntfs_memstathfs+hfsplus_memstat default_memstat 为其上所有 系统/模块 内存跟踪统计,跟踪日志在memstat结构的 log结构中,如下图

说明:

       int calloc; 

       int malloc; 

       int total;   

int realloc;

       int max_once;

       int max_total; 

 

       int calloc_cnt;

       int malloc_cnt;

       int realloc_cnt;

       int total_cnt;

       int max_total_cnt; 同上

 

              int   flag;  //日志系内部标志

              u16  limit;  //系统/模块最多可容纳 日志数 ,MAX_ALLOC_ENTRYS有关

              s16  max_index;        //最后一个 占用日志 索引

              u16  free_index;        //第一个 空闲日志 索引

              u16 alloc_count;        //迄今 已使用 日志数

              u16 max_alloc_count;    //迄今 使用最多日志情况下的 日志数

              char *logstr[MAX_ALLOC_ENTRYS]; //日志数组

 

如果内存申请成功 ,写入有效日志, 第三个逗号后为 申请内存处 所在文件的 文件名字符串; 第二个逗号后为 申请内存处 该文件的所在行数; 第一个逗号后为在该处 申请的总有效字节数;第一个逗号内为在该处 申请的总有效次数,

如果 该文件 对应 line 的申请 内存被全部释放 , 该处日志 = 0,如上图131516

 

如果 系统/模块退出, 所有内存释放 ,如下图,可以用来跟踪 系统/模块 内存的泄漏情况

 

 

 

从上图可以看看出 ,系统在任何调试时刻 ,可以看到模块占用的内存统计和申请情况。大大方便 用户对 模块使用内存的评估

 

 

如何使用:

举例如下:假设要评估的模块是 hfs 

 

 

1)在 评估 模块或系统的 *.h*.c 文件,   加入

#define   TRACE_MEMSTAT  & hfs_memstat       //指定跟踪模块命名

#define MAX_ALLOC_ENTRYS    128 //FS_TRACE_MEM =2有用

#include "mtarce.h"

 

DECLARE_MEMSTAT(hfs_memstat);

 

#define hfs_calloc( size)    mtrace_calloc(size)

#define hfs_malloc( size)   mtrace_malloc(size )

#define hfs_realloc(p, size)  mtrace_realloc( p ,size )

#define hfs_free(p)        mtrace_free(p) 

 

确保每个c文件 都会编译到

 

2)某一c文件 加入

DEFINE_MEMSTAT ((hfs_memstat);  //仅需一次

 

3)系统项目编译链接 加入 mtrace.c 文件

 

4)以后在模块(或系统)凡是使用 内存分配例程的地方 都替换为 mtrace_XXX hfs_XXX对应例程。

 

通过以上几步,即可应用mtrace 的动态 内存使用情况 跟踪

 

源码说明:

mtrace 2个可移植文件组成mtrace.h, mtrace.c

其中mtrace.h 为算法配置和声明,mtrace.c为算法实现,详细列举如下

 

 

mtrace.h

 

 

#ifndef MTRACE_H

#define MTRACE_H

 

configurations

 

#include "includes.h"

 

#ifndef __ROM_

   #define  FS_PRINT_ENABLE   1

   #define  FS_TRACE_MEM   2                   //0,1,2,3 可以修改

#else   //dont modify below

   #define  FS_PRINT_ENABLE   0       

   #define  FS_TRACE_MEM   0         

#endif

 

//fit to yourown requirement

#define  u16   unsigned  short

#define  s16   signed  short

//#define  size_t   int

 

#define raw_calloc( n , size)    calloc(n ,size)

#define raw_malloc( size)        malloc(size)

#define raw_realloc(p, size)     realloc( p ,size )

#define raw_free(p)              free( p )

 

#define DECLEAR_KEY(x)           OS_EVENT  *x=NULL

#define INIT_THREAD_MUTEX()      AVSemCreate(1)

#define THREAD_LOCK(x) {INT8U  err;   AVSemPend(x,0,&err); }

#define THREAD_UNLOCK(x) AVSemPost(x)  

 

#ifndef ENOMEM

       #define   ENOMEM 12

#endif

 

#if( FS_TRACE_MEM>2)

 

#define mtrace_calloc(n, size)   raw_calloc(n,size)

#define mtrace_malloc( size)     raw_malloc(size)

#define mtrace_realloc(p, size)  raw_realloc( p ,size )

#define mtrace_free(p)           raw_free( p )

 

#define   DEFINE_MEMSTAT(stat)   //

#define   DECLARE_MEMSTAT(stat)  //

 

 

#elif( FS_TRACE_MEM>0)

 

#ifndef MAX_ALLOC_ENTRYS 

       #define MAX_ALLOC_ENTRYS   128

#endif

 

 

#define memstat_log_inited    0x01

#define memstat_log_allused  0x02

 

#define memstat_log_index_invalid  -1

 

typedef struct

{

       int calloc;

       int malloc;

       int realloc;

       int total;

      

       int max_once;

       int max_total;

 

       int calloc_cnt;

       int malloc_cnt;

       int realloc_cnt;

       int total_cnt;

       int max_total_cnt;

 

       struct

       {

              int   flag;

              u16  limit;

#if( FS_TRACE_MEM&2)

              s16  max_index;        //max valid index,fast find  matched log        

              u16  free_index;        //first free log index,fast for next log if all exist not matched

              u16 alloc_count;        //current total used log entrys

              u16 max_alloc_count;

              char *logstr[MAX_ALLOC_ENTRYS];

//            char **logstr;

#endif

       }log;      

}memstat;

 

#define   DEFINE_MEMSTAT(hinst)   memstat hinst={0,0,0,0,0, 0,0,0,0,0,0,{0,MAX_ALLOC_ENTRYS}}

#define   DECLARE_MEMSTAT(hinst)  extern  memstat hinst

 

void *mtrace_calloc_trace(size_t size,int line, char *file,memstat *stat);

void *mtrace_malloc_trace(size_t size,int line, char *file,memstat *stat);

void *mtrace_realloc_trace(void *old,size_t size,int line, char *file,memstat *stat);

void mtrace_free_trace (void *pmen,memstat *stat);

 

 

DECLARE_MEMSTAT(default_memstat);

 

#ifndef TRACE_MEMSTAT 

 #define TRACE_MEMSTAT &default_memstat

#endif

 

#define mtrace_calloc( size)      mtrace_calloc_trace(size, __LINE__, __FILE__,TRACE_MEMSTAT)

#define mtrace_malloc( size)     mtrace_malloc_trace(size ,__LINE__, __FILE__,TRACE_MEMSTAT)

#define mtrace_realloc(p, size) mtrace_realloc_trace( p ,size ,__LINE__, __FILE__,TRACE_MEMSTAT)

#define mtrace_free(p)              mtrace_free_trace( p , TRACE_MEMSTAT)

 

 

#else

 

#define   DEFINE_MEMSTAT(stat)       //

#define   DECLARE_MEMSTAT(stat)  //

 

void *mtrace_calloc(size_t size);

void *mtrace_malloc(size_t size);

void *mtrace_realloc(void *old,size_t size);

void mtrace_free(void pmen);

 

#endif

 

#if FS_PRINT_ENABLE

 

void mtrace_log_redirect(const char *function, const char *file, int line,

       const char *FORMAT, ...);

#define mtrace_log_error(arg...) mtrace_log_redirect(__FUNCTION__,__FILE__,__LINE__,arg)

 

#else

 

#define mtrace_log_error(arg...) //           

 

#endif 

 

#endif 

 

 

mtarce.c

 

 

#include " mtarce.h"

 

#if( FS_TRACE_MEM>2)

 

#elif( FS_TRACE_MEM>0)

#define  MH_MAGIC_MASK  0xFFFFFFFC

#define  MH_TYPE_MASK     0x00000003

 

#define  MH_MAGIC_ALLOC             0xCCCCCCCC

#define  MH_MAGIC_FREE             0xEEEEEEEE

#define  MH_TYPE_C 0

#define  MH_TYPE_M 1

#define  MH_TYPE_R 2

 

typedef struct

{

 int magic;           //0xcccc , low nibble for type

 int size;

 

#if( FS_TRACE_MEM&2)

 int logindex;

#endif

}meminfo;

 

#define  meminfo_len  sizeof(meminfo)

 

DEFINE_MEMSTAT(default_memstat);            //total mem trace for all fs

 

#if( FS_TRACE_MEM&2)

 

#define MAX_ALLOC_FILNAMELEN 32

#define MEMLOG_STATEFREE    0

 

#define thisfs_memlog   stat->log

 

static void init_memlog(memstat *stat)

{

 if(thisfs_memlog.flag&memstat_log_inited)

  return ;

 

#if 0

 char *log =(char *) calloc(sizeof(char *),thisfs_memlog.limit);

 if (!log)

 {

  fs_log_error("Failed to calloc %d logs/n", (long)thisfs_memlog.limit);

  return ;

 }

 

 thisfs_memlog.logstr = (char **)log;

#endif

 

 for (int i=0;i<thisfs_memlog.limit;i++)

 {

  thisfs_memlog.logstr[i]=MEMLOG_STATEFREE;

 }

 thisfs_memlog.max_index=-1;

 thisfs_memlog.free_index=0;

 thisfs_memlog.alloc_count=0;

 thisfs_memlog.flag |= memstat_log_inited;

 

 if(default_memstat.log.flag&memstat_log_inited)

  return ;

 

// default_memstat.log.limit = MAX_ALLOC_ENTRYS;

#if 0

 log =(char *) calloc(sizeof(char *),default_memstat.log.limit);

 if (!log)

 {

  fs_log_error("Failed to calloc %d logs/n", (long)default_memstat.log.limit);

  return ;

 }

 thisfs_memlog.logstr = (char **)log;

#endif

 

 for ( i=0;i < default_memstat.log.limit;i++)

 {

  default_memstat.log.logstr[i]=MEMLOG_STATEFREE;

 }

 default_memstat.log.max_index=-1;

 default_memstat.log.free_index=0;

 default_memstat.log.alloc_count=0;

 default_memstat.log.flag |= memstat_log_inited;

 

}

 

 

#define logfmt "%d,%d,%d,%s"

static size_t o_size;

static char o_file[MAX_ALLOC_FILNAMELEN];

static int o_line;

static int o_times;

 

static int register_log(meminfo *p,int line, char *file,memstat *stat)

{

 

 int i,j;

 int  retry=0;

 size_t size  = p ->size;

 

 init_memlog(stat);

 

 p->logindex = memstat_log_index_invalid;

 

//use the already log entry

 for( i=0 ;i<=thisfs_memlog.max_index;i++)

 {

 

  if(thisfs_memlog.logstr[i]==MEMLOG_STATEFREE)

   continue;

 

  sscanf(thisfs_memlog.logstr[i],  logfmt,  &o_times,&o_size,&o_line, o_file);

  if(/*(o_size==size)&&*/(o_line==line)&&(strcmp(o_file,file)==0)&&o_times>0)

  {

   snprintf((char*) thisfs_memlog.logstr[i] ,MAX_ALLOC_FILNAMELEN, logfmt ,o_times+1,size+o_size,line,file);

   p->logindex = i;

   return 0;

  }

 }

 

 

//create new log

 if(thisfs_memlog.flag & memstat_log_allused)

 {

  fs_log_info("all used out now/n");

  return -1;

 }

 

 j=thisfs_memlog.free_index;

 if(thisfs_memlog.logstr[j])

 {

  fs_log_error("already used now/n");

  for(i = 0 ;i<thisfs_memlog.limit;i++)

  {

   if(thisfs_memlog.logstr[i]==MEMLOG_STATEFREE)

    break;

  }

  if(i==thisfs_memlog.limit)

  {

   thisfs_memlog.flag|=memstat_log_allused;

   fs_log_error("all used out now/n");

   return -1;

  }

  j = i;

 }

 

 thisfs_memlog.logstr[j] =(char *)  malloc(MAX_ALLOC_FILNAMELEN);

 if (!thisfs_memlog.logstr[j])

 {

  fs_log_error("Failed to malloc %d bytes/n", (long)size);

  thisfs_memlog.free_index = i; 

  return -1; 

 }

 snprintf((char*) thisfs_memlog.logstr[j] ,MAX_ALLOC_FILNAMELEN, logfmt, 1,size,line,file);

 thisfs_memlog.alloc_count++;

 if(thisfs_memlog.max_alloc_count<thisfs_memlog.alloc_count)

  thisfs_memlog.max_alloc_count=thisfs_memlog.alloc_count;

 

 if(thisfs_memlog.max_index< j)

  thisfs_memlog.max_index=j;

 

 i = j+1 ;

again:

 if(thisfs_memlog.alloc_count<thisfs_memlog.limit)              //find free log entry

 {

  for(  ;i<thisfs_memlog.limit;i++)

  {

   if(thisfs_memlog.logstr[i]==MEMLOG_STATEFREE)

    break;

  }

  if(i==thisfs_memlog.limit )

  {

         if(!retry)

         {

    i =  0 ;

    retry = 1;

    goto again;

         }

   thisfs_memlog.flag|=memstat_log_allused;

  }

  else  

  {

   thisfs_memlog.free_index = i;

  }

 }

 else

 {

  thisfs_memlog.flag |= memstat_log_allused; 

 }

 p->logindex = j;

 return  0;

}

 

static void unregister_log(meminfo *p,memstat *stat)

{

 int j = p->logindex;

 

 if(j<0||j>thisfs_memlog.limit)

  return ;

 

 if(thisfs_memlog.logstr[j]==MEMLOG_STATEFREE)

 {

  fs_log_error("already free /n");

  return ;

 }

 

 sscanf(thisfs_memlog.logstr[j],  logfmt,  &o_times,&o_size,&o_line, o_file);

 if(o_times>1)

 {

  snprintf((char*) thisfs_memlog.logstr[j] ,MAX_ALLOC_FILNAMELEN, logfmt ,--o_times,o_size-p->size,o_line,o_file);

  return ;

 }

 

 free(thisfs_memlog.logstr[j]);

 thisfs_memlog.logstr[j]=MEMLOG_STATEFREE;

 if((thisfs_memlog.flag & memstat_log_allused) || (thisfs_memlog.free_index> j))

  thisfs_memlog.free_index =j;

 thisfs_memlog.alloc_count--;

 

 if(thisfs_memlog.max_index== j)

 {

  for( ;j>=0&&thisfs_memlog.logstr[j]==MEMLOG_STATEFREE;j--)

   thisfs_memlog.max_index--;

 }

 

 thisfs_memlog.flag &=~memstat_log_allused; 

}

 

 

#endif

 

static OS_EVENT  *memstat_sem=NULL;  // perhaps  this module will run on multiple task

static int  updata_meminfo(meminfo *p,int type,int size ,memstat *stat ,int line, char *file)

{

 int ret =0;

 

 if(!memstat_sem)

 {

  memstat_sem = AVSemCreate (1);

  if(!memstat_sem)

  {

   fs_log_error("memstat_sem create fail/n", p);

   return -1;

  }

 }

 

 AVSemPend(memstat_sem,0,(INT8U *) &ret);

 if(size>0)

 {

  p->magic=MH_MAGIC_ALLOC|type;

  p->size=size;

 

  if(type==MH_TYPE_C)

  {

   stat->calloc+=size;

   stat->calloc_cnt++;

  }

  else if(type==MH_TYPE_M)

  {

   stat->malloc +=size ;

   stat->malloc_cnt++;

  }

  else

  {

   stat->realloc+=size;

   stat->realloc_cnt++;

  }

  

  stat->total+=size;

  stat->total_cnt++;

 

  if(stat->max_once<size)

  stat->max_once=size;

  if(stat->max_total<stat->total)

  stat->max_total=stat->total;

  if(stat->max_total_cnt<stat->total_cnt)

  stat->max_total_cnt=stat->total_cnt;

 

  if(stat!= &default_memstat)

  {

   if(type==MH_TYPE_C)

   {

    default_memstat.calloc+=size;

    default_memstat.calloc_cnt++;

   }

   else  if(type==MH_TYPE_M)

   {

    default_memstat.malloc +=size ;

    default_memstat.malloc_cnt++;

   }  

   else

   {

    default_memstat.realloc+=size;

    default_memstat.realloc_cnt++;

   }

   

   default_memstat.total+=size; 

   default_memstat.total_cnt++;

  

   if(default_memstat.max_once<size)

   default_memstat.max_once=size;

   if(default_memstat.max_total<default_memstat.total)

   default_memstat.max_total=default_memstat.total;

  

   if(default_memstat.max_total_cnt<default_memstat.total_cnt)

   default_memstat.max_total_cnt=default_memstat.total_cnt;

  }

 

#if( FS_TRACE_MEM&2)

  ret = register_log(p,line,file,stat);

#endif

 

 }

 else

 {  

  type = p->magic&MH_TYPE_MASK;

  if(p->magic==MH_MAGIC_FREE)

  {

   fs_log_error("free 0x%x again/n", p);

   AVSemPost(memstat_sem);

   return -1;

  }

  if((p->magic&MH_MAGIC_MASK)!=MH_MAGIC_ALLOC||type>MH_TYPE_R||type<MH_TYPE_C)

  {

   fs_log_error("free 0x%x mismatch/n", p);

   AVSemPost(memstat_sem);

   return -1;

  }

  p->magic=MH_MAGIC_FREE;             //avoid  free again

 

        size=p->size;

  if(type==MH_TYPE_C)

  {

   stat->calloc -=size ;

   stat->calloc_cnt--;

  }

  else if(type==MH_TYPE_M)

  {

   stat->malloc -=size ;

   stat->malloc_cnt--;

  }

  else

  {

   stat->realloc -=size ;

   stat->realloc_cnt--; 

  }

  stat->total-=size ;

  stat->total_cnt--;

 

  if(stat!= &default_memstat)

  {

   if(type==MH_TYPE_C)

   {

    default_memstat.calloc -=size ;

    default_memstat.calloc_cnt--;

   }

   else if(type==MH_TYPE_M)

   {

    default_memstat.malloc -=size ;

    default_memstat.malloc_cnt--;

   }

   else

   {

    default_memstat.realloc -=size ;

    default_memstat.realloc_cnt--;   

   }

   default_memstat.total-=size ;

   default_memstat.total_cnt--;

  }

 

#if( FS_TRACE_MEM&2)

  unregister_log(p,stat);

#endif

 

 }

 

 AVSemPost(memstat_sem);

 return ret;

}

 

/**

* ntmtrace_calloc

*

* Return a pointer to the allocated memory or NULL if the request fails.

*/

void *mtrace_calloc_trace(size_t size,int line, char *file,memstat *stat)

{

 

 meminfo *p;

 

 if(size<=0)

 return NULL;

 

 p =(meminfo *) calloc(1,size+meminfo_len);

 if (!p)

 {

  fs_log_error("Failed to calloc %d bytes/n", (long)size);

  errno = ENOMEM;

  return NULL;

 }

 

 updata_meminfo(p, MH_TYPE_C,size,stat,line,file);

 

 return (char *)p+meminfo_len;

}

 

 

void *mtrace_malloc_trace(size_t size,int line, char *file,memstat *stat)

{

 

 meminfo *p;

 

 if(size<=0)

 return NULL;

 

 p =(meminfo *)  malloc(size+meminfo_len);

 if (!p)

 {

  fs_log_error("Failed to malloc %d bytes/n", (long)size); 

  errno = ENOMEM;

  return NULL;

 }

 

 updata_meminfo(p, MH_TYPE_M,size,stat,line,file);

 

 return (char *)p+meminfo_len;

}

 

 

void *mtrace_realloc_trace(void *old,size_t size,int line, char *file,memstat *stat)

{

 

 meminfo *p;

 int type ;

 

 if(size<=0)

 return NULL;

 

 if(old)

 {

  p =(meminfo *) ((char *)old -meminfo_len) ;

 

  if(updata_meminfo(p, -1,-1,stat , -1 ,NULL)!=0)

  {

   return NULL;  

  }

  type = MH_TYPE_R ;

 }

 else

 {

  p = NULL;

  type = MH_TYPE_M ;

 }

 p =(meminfo *)  realloc(p,size+meminfo_len);

 if (!p)

 {

  fs_log_error("Failed to realloc %d bytes/n", (long)size);

  errno = ENOMEM;

  return NULL;

 }

 

 updata_meminfo(p, type,size,stat,line,file);

 

 return (char *)p+meminfo_len;

}

 

void mtrace_free_trace(void *pmen,memstat *stat)

{

 

 if(pmen)

 {

  meminfo *p =(meminfo *) ((char *)pmen -meminfo_len) ;

 

  if(updata_meminfo(p, -1,-1,stat, -1,NULL)==0)

  {

   free(p);

  }

 }

}

 

#else

 

 

void *mtrace_calloc(size_t size)

{

 

 void *p;

 

 if(size<=0)

 return NULL;

 

 p = calloc(1,size);

 if (!p)

 {

  fs_log_error("Failed to calloc %d bytes/n", (long)size);

  errno = ENOMEM;

 }

 

 return p;

}

 

 

void *mtrace_malloc(size_t size)

{

 void *p;

 

 if(size<=0)

 return NULL;

 

 p = malloc(size);

 if (!p)

 {

  fs_log_error("Failed to malloc %d bytes/n", (long)size);

  errno = ENOMEM;

 }

 

 return p;

}

 

 

void *mtrace_realloc(void *old,size_t size)

{

 

 void *p;

 

 if(size<=0)

 return NULL;

 

 p = realloc(old,size);

 if (!p)

 {

  fs_log_error("Failed to realloc %d bytes/n", (long)size);

  errno = ENOMEM;

 }

 

 return p;

}

 

void mtrace_free(void *pmen)

{

 if(pmen)

 {

  free(pmen);

 }

}

#endif

 

#if FS_PRINT_ENABLE

extern INT32U  OSTime ;

void mtrace_log_redirect(const char *function, const char *file, int line,

       const char *FORMAT, ...)

{

  OS_CPU_SR cpu_sr;

//  INT32U  tick = AVTimeGet();           //avoid  OS_EXIT_CRITICAL in printf

  va_list args;

 

  OS_ENTER_CRITICAL();

  printf("/n%s (%s,line=%d,tick=%d)/n--" ,function,file,line,OSTime);

  va_start (args, FORMAT);

  vprintf (FORMAT, args);

  va_end (args);

  OS_EXIT_CRITICAL();

   

}

#endif

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值