C column of Pointer <3> malloc() free()

What a nice day, ok ,let us go on the malloc() and free() chapter, if you have seen the previous chapter "Pointer <2>",you may think"Come on ! you have introduce,why again?", i really want tell you "That just a cup of water vs a river". If you just the first time to see this chapter, please refer to the "C column of Pointer <2> malloc() free()" ( link here  点击打开链接 ).

Ok, why we go on the malloc() and free() , if you try to run previous chapter example, you may be confused by the result , now I rewrite  as below

/*A bad method to allocate and free required block in heap*/
#include <stdlib.h>  
#include <stdio.h>  
int main(void)  
{  
  int *pf,*sp;  
  pf = malloc(4*sizeof(int));  
  if( pf == NULL)  
  {  
     printf("Out of memory!\n");  
     exit(1);  
  }  
  sp = pf;  
  *sp = 100;  
  *++sp = 200;//As operate precedence *++sp = *(++sp)  
  *++sp = 300;  
  sp = pf;  
  printf("The content is %d %d %d\n",*++sp,*++sp,*sp);  
  
  printf("Before free pf = %p\n",pf);  
  /*Do not need the 4*4 bytes*/  
  free(pf);
  printf("After free pf = %p\n",pf);
  printf("sp's content is %d %d\n",*--sp,*sp);//After free the required block, can we handle the pointer variable pf ??  
  return 0; 
} 


Here i add the printf() at the bottom of the program, the result as below shows


From the result we can make a conclusion , although we have freed the required block in memory (heap),but

0) the pointer variable pf is not cleared and 

1) some data still in the memory , not clear or disappear.

That why we should you memset() function to initialize the newly allocated block. Ok , we have found so many hidden problem in our code, just rewrite it as below

/* A perfect method to allocate a requested block then free it in heap 
*/  
  
#include <stdlib.h>  
#include <stdio.h> 
 
int main(void)  
{  
  int *pf,*sp;  
  pf = malloc(4*sizeof(int));  
  if( pf == NULL)  
  {  
     printf("Out of memory!\n");  
     exit(1);  
  } 
  memset(pf,0,4*sizeof(int)); //Initialize to zero
  sp = pf;  
  *sp = 100;  
  *++sp = 200;//As operate precedence *++sp = *(++sp)  
  *++sp = 300;  
  sp = pf;  
  printf("The content is %d %d %d\n",*++sp,*++sp,*sp);  
    
  /*Do not need the 4*4 bytes*/  
  free(pf);
  pf = null;//point to a null
  
  return 0;  
} 

Ok, if we use  malloc() function frequently , and forget free the unused block or all the blocks allocated are in need, then the memory will have a lot of segments .We know that the memory size is constant. The more we allocate a block form memory the smaller the remained size will be. So now, let us code to simulate this process which the malloc() function could not allocate the required block in memory ,because we have used a lot of blocks but not free.

/* This program is used to simulate the allocation but never free in heap
 * and there are a lot of memory fragments in our memory not to be freed.
 * At last ,there are not enough block to be allocated in memory.
 */
#include <stdio.h>
#include <stdlib.h>


int main(void)
{
  int count = 1;

  int *p = malloc(4000000*sizeof(int));//4*4000000 bytes = 15625 K bytes = 15 M bytes

  while(p != NULL)
  {
    printf("Address : %p \n",p);
    count++;
    p = malloc(count*4000000*sizeof(int)); // count times 15M  bytes 
  }
  printf("Have allocated %d times but free none !\n",count);

  return 0;
}

And the result (linux os. and gcc compiler)as follows


In this program ,we have allocated 19 times, but the 20 time allocated failure.So when we use the malloc() and free() functions to manage the memory , we must pay attention to the free. Just like "Press the accelerator easily, but don't forget where is the brake!" 

Next chapter the column of Pointer ,i want to design a  management memory (only with heap) program, although maybe that's a hard task, but i can try. let us keep moving.

These days i'm a little busy , and not enough time to write this blog,  but i 'm sure i will go on to share my experience about the C program in this blog , no matter how hard , how busy ,what a  less time!

Have a nice day,every one !


#include "cuda_runtime.h" #include "device_launch_parameters.h" #include <iostream> #include <ctime> using namespace std; cudaError_t CudaCheck(float* matrix_1, float* matrix_2, float* matrix_3, int row, int column); void MatrixSumOnCPU(float* matrix_1, float* matrix_2, float* matrix_3, int row, int column); __global__ void MatrixSumOnGPU(float* matrix1, float* matrix2, float* matrix3, int row, int column); int main() { int row = 1 << 8; int column = 1 << 6; int byte_num = row * column * sizeof(float); float* matrix_1 = (float*)malloc(byte_num); float* matrix_2 = (float*)malloc(byte_num); float* matrix_3_GPU = (float*)malloc(byte_num); float* matrix_3_CPU = (float*)malloc(byte_num); float* matrix_1_pointer = matrix_1; float* matrix_2_pointer = matrix_2; srand(time(NULL)); for (int j = 0; j < row; j++) { for (int i = 0; i < column; i++) { matrix_1_pointer[i] = (float)rand(); matrix_2_pointer[i] = (float)rand(); } matrix_1_pointer += column; matrix_2_pointer += column; } cudaError_t cudaStatus = CudaCheck(matrix_1, matrix_2, matrix_3_GPU, row, column); if (cudaStatus != cudaSuccess) { std::cerr << "CudaCheck failed" << std::endl; return 1; } cudaStatus = cudaDeviceReset(); if (cudaStatus != cudaSuccess) { std::cerr << "CudaCheck failed" << std::endl; return 1; } MatrixSumOnCPU(matrix_1, matrix_2, matrix_3_CPU, row, column); free(matrix_1); free(matrix_2); free(matrix_3_GPU); free(matrix_3_CPU); free(matrix_1_pointer); free(matrix_2_pointer); return 0; } void MatrixSumOnCPU(float* matrix_1, float* matrix_2, float* matrix_3, int row, int column) { float* matrix_a = matrix_1; float* matrix_b = matrix_2; float* matrix_c = matrix_3; clock_t cpu_start = clock(); for (int j = 0; j < row; j++) { for (int i = 0; i < column; i++) { matrix_c[i] = matrix_a[i] + matrix_b[i]; } matrix_a += column; matrix_b += column; matrix_c += column; } clock_t cpu_end = clock(); double total_time = double(cpu_end - cpu_start) / CLOCKS_PER_SEC; std::cout << "the execution time of CPU is " << total_time << std::endl; free(matrix_a); free(matrix_b);//在这报错 已在 CudaMatrixSum.exe 中执行断点指令(__debugbreak()语句或类似调用) free(matrix_c); return; } __global__ void MatrixSumOnGPU(float* matrix_1, float* matrix_2, float* matrix_3, int row, int column) { /* int block_id = blockIdx.x + blockIdx.y * gridDim.x + blockIdx.z * gridDim.y * gridDim.x; int thread_id = threadIdx.x + threadIdx.y * blockDim.x + threadIdx.z * blockDim.y * blockDim.x; int thread_num_per_block = blockDim.x * blockDim.y * blockDim.z; int idx = thread_id + thread_num_per_block * block_id; */ int ix = threadIdx.x + blockIdx.x * blockDim.x; int iy = threadIdx.y + blockIdx.y * blockDim.y; int idx = ix + iy * row; if (ix < column && iy < row) { matrix_3[idx] = matrix_1[idx] + matrix_2[idx]; } } cudaError_t CudaCheck(float* matrix_1, float* matrix_2, float* matrix_3, int row, int column) { float* device_matrix_1 = 0; float* device_matrix_2 = 0; float* device_matrix_3 = 0; int byte_num = row * column * sizeof(float); cudaError_t cudaStatus; cudaStatus = cudaSetDevice(0); if (cudaStatus != cudaSuccess) { std::cerr << "cudaSetDevice failed" << std::endl; goto Error; } cudaStatus = cudaMalloc((float**)&device_matrix_1, byte_num); if (cudaStatus != cudaSuccess) { std::cerr << "cudaMalloc failed" << std::endl; goto Error; } cudaStatus = cudaMalloc((float**)&device_matrix_2, byte_num); if (cudaStatus != cudaSuccess) { std::cerr << "cudaMalloc failed" << std::endl; goto Error; } cudaStatus = cudaMalloc((float**)&device_matrix_3, byte_num); if (cudaStatus != cudaSuccess) { std::cerr << "cudaMalloc failed" << std::endl; goto Error; } cudaStatus = cudaMemcpy(device_matrix_1, matrix_1, byte_num, cudaMemcpyHostToDevice); if (cudaStatus != cudaSuccess) { std::cerr << "cudaMemcpy failed" << std::endl; goto Error; } cudaStatus = cudaMemcpy(device_matrix_2, matrix_2, byte_num, cudaMemcpyHostToDevice); if (cudaStatus != cudaSuccess) { std::cerr << "cudaMemcpy failed" << std::endl; goto Error; } dim3 block(32, 32); dim3 grid((column - 1) / block.x + 1, (row - 1) / block.y + 1); clock_t gpu_start = clock(); MatrixSumOnGPU <<<grid, block >>> (device_matrix_1, device_matrix_2, device_matrix_3, row, column); clock_t gpu_end = clock(); double total_time = double(gpu_end - gpu_start) / CLOCKS_PER_SEC; std::cout << "the execution time of GPU is " << total_time << std::endl; cudaStatus = cudaGetLastError(); if (cudaStatus != cudaSuccess) { std::cerr << "cudaGetLastError failed" << std::endl; goto Error; } cudaStatus = cudaDeviceSynchronize(); if (cudaStatus != cudaSuccess) { std::cerr << "cudaDeviceSynchronize failed" << std::endl; goto Error; } cudaStatus = cudaMemcpy(matrix_3, device_matrix_3, byte_num, cudaMemcpyDeviceToHost); if (cudaStatus != cudaSuccess) { std::cerr << "cudaMemcpy failed" << std::endl; goto Error; } Error: cudaFree(device_matrix_1); cudaFree(device_matrix_2); cudaFree(device_matrix_3); return cudaStatus; }
最新发布
10-22
文件str.h内容 #include <stdlib.h> #include <string.h> #include <stdio.h> char *mystrstr(const char *s1,const char *s2,int len1,int len2);//s1= source, s2= template len1= strlen(s1); char* mystrchr(char *s, char c,int len); int mystrncmp(const char *s1,const char *s2,int n);//,int d); 文件str.c内容 #include "str.h" /* void main() { char *s1="hello world,\0 hello you,\0 hello me\0, hello her"; char *s2="her"; char *temp=mystrstr(s1,s2,strlen(s1),strlen(s2)); printf("%s\n",temp); }*/ char *mystrstr(const char *s1,const char *s2,int len1,int len2)//return point on success, 0 on fail. //s1= source, s2= template len1= strlen(s1) { char *p=(char*)s1; //const size_tlen=strlen(s2); for(;(p=mystrchr(p,s2[0],len1))!=NULL;p++) { if(mystrncmp(p,s2,len2)==0) return (char*)p; } return(0); } char* mystrchr(char *s, char c,int len)// return 0 for fail, pointer on success { while(len!=0 && *s != c) { ++s; --len; } return *s==c ? s : NULL; } int mystrncmp(const char *s1,const char *s2,int n)//,int d)// return 0 if eqal, -1 for less , 1 for more { int i; for(i=0;i<n;i++)//字符串前面部分都相同 { if(s1[i]>s2[i]) { return 1; } else { if(s1[i]<s2[i]) return -1; else continue; } /*if(d==1) { printf("%c==%c\n",s1[0],s2[0]); return 0; }*/ /* if(*s1-*s2>0) return 1; if(*s1-*s2<0) return -1; //s1++; //s2++; */ } return 0; } 文件func.h内容 #include <sys/types.h> #include <stdlib.h> #include <dirent.h> int is_dir_exist(const char * dir_path); 文件func.c内容 #include "func.h" int is_dir_exist(const char * dir_path) { if(dir_path==NULL) return 0; if(opendir(dir_path)==NULL) return 0; return 1; } 文件bin.h内容 #include <stdio.h> #include <stdlib.h> //#include <unistd.h> #include<stdlib.h> #include "str.h" #include <time.h> #define FILE_HEADER_SIZE 11 const char FILE_HEADER[]={0x17,0xfa,0xae,0x4e,0x0b,0x09,0x65,0x6e,0x76,0x00,0xa}; #define RECORD_HEAD_SIZE 5//09+tag+00+09 #define RECORD_TAG_SIZE 14//GL-C001+0x00+09+fmt+00+04 #define RECORD_TAG_OK_SIZE 9//OK+0x00+09+fmt+00+04 #define RECORD_FMT_SIZE 7//&#39;C&#39;+09+typ+00+09 #define RECORD_MIN_SIZE 60 #define RECORD_DAT_SIZE 6//00+09+dat+00 //#define RECORD_TYP_SIZE 6//GL-C001+0x00 //const char RECORD_HEADER[]={0x06,0x59,0xc1,0x05,0x29,/*0x04,*/0x03,0x00}; const char RECORD_HEADER[]={0x74,0x61,0x67,0x00,0x09};//0x09- const char END_R_A_LINE[]={0x00,0x00}; const char GLC[]="KV-C58B"; const char WA[]="KA-C902"; const char WAM[]="KV-C204"; const char LOT[]="KM-C102"; const char IMAGE[]="KI-E103"; const char FMT_A_HEADER[]="\v\t"; //int fseek(FILE *stream, long offset, int fromwhere);函数设置文件指针stream的位置。 #define BODY_BUFF_SIZE 1024*1024*512 #define HEAD_BUFF_SIZE 256 #define STRING 0x09 //HT #define INT 0x06 //ACK #define SHORT 0x05 //ENQ #define LFB 0x0b // VT #define CRA 0x0a //LF #define TIME 0x06 //ACK #define DOUBLE 0x08 //BS #define FLOAT 0x07 //?? #define CHAR 0x04 //EOT #define ZERO 0x02 //STX #define ONE 0x03 //ETX #define N0 0x00 //NUT //#define DEBUG //#define LOG #ifdef DEBUG #define printd(format, arg...) \ printf(format, ## arg) #else #define printd(format, arg...) #endif #ifdef LOG #define printl(format, arg...) \ printf(format, ## arg) #else #define printl(format, arg...) #endif typedef struct record_WAM { int wafer_no; int chuck_id; unsigned int max_nr_of_x_gridlines; unsigned int max_nr_of_wams; unsigned int nr_of_wams; double x_gridline_offsets[9]; struct wam_shot *shots; struct record_WAM * next; }record_WAM; typedef struct wam_node { double z; int valid; }wam_node; // wam->next -->wam --> next -->wam (wafer_no, chuck id,etc) :wafer 1 --> wafer 2 ... wafer 25 // | // shots-->next-->wam_shot // | // wam_shot(sid, cx,cy, x grid, y grid,etc) : shot1 --> shot2 ... shot106 ... // | // nodes (z, valid) // | // wam_node[(xgrid+1) * (ygrid+1)] : shot1 --> shot2 ... shot106 ... typedef struct wam_shot { unsigned int id; wam_node * nodes; struct wam_shot * next; double cx,cy; double x_gridlines_shift; unsigned int nr_of_x_gridlines; unsigned int nr_of_y_gridlines; double x_gridline_offsets[9]; double y_gridline_offsets[70]; }wam_shot; typedef struct record_tag { char tag[10]; int count; char typ[128]; struct record_tag *next; }record_tag; typedef struct record_head { char *head; char *tag; char fmt; char *typ; int typ_len; int size; struct record_head *next; } record_header; void free_wam(void); 文件bin.c内容 //查到C语言中fread的定义:size_t fread( void* buffer, size_t size,size_t count, FILE* stream ); #include "bin.h" #include "func.h" char *filename=NULL; FILE *fp=NULL; record_header * record_set_head=NULL; record_tag * tag_set_head=NULL; record_WAM * wam_set_head=NULL; char head_buff[HEAD_BUFF_SIZE]; char body_buff[BODY_BUFF_SIZE]; int open_mdl_file(char * filename) { fp=fopen(filename,"rb"); printf("mdl_file=%s!\n",filename); return fp!=NULL; } int close_mdl_file() { if(fp==NULL) { printf("No file opened, nothing to close!\n"); return 0; } else { fclose(fp); printf("MDL file has been closed!\n"); fp=NULL; return 1; } } int read_file_header() { int i; int len; if(fp) { if(0==(len=fread(head_buff,sizeof(char),FILE_HEADER_SIZE,fp))||len!=FILE_HEADER_SIZE) { printf("Read header buffer failed!\n"); return 0; } else { if(0==mystrncmp(head_buff,FILE_HEADER,FILE_HEADER_SIZE)) { printf("good header\n"); return 1; } else { printf("bad header\n"); return 0; } } } else { printf("no file opened!\n"); return 0; } } int parse_record(record_header *p) { //p->head = temp; int found=0; p->tag = p->head + RECORD_HEAD_SIZE; if(strcmp(p->tag,"OK")==0) { p->fmt = *(p->tag + RECORD_TAG_OK_SIZE); p->typ = p->tag + RECORD_TAG_OK_SIZE + RECORD_FMT_SIZE; } else { p->fmt = *(p->tag + RECORD_TAG_SIZE); p->typ = p->tag + RECORD_TAG_SIZE + RECORD_FMT_SIZE; } p->typ_len=strlen(p->typ); record_tag * tp=tag_set_head; if(tag_set_head==NULL)//not found a matched tag { tag_set_head=malloc(sizeof(record_tag)); tp=tag_set_head; strcpy(tp->tag,p->tag); //printf("len= %d\n",strlen(p->typ)); strncpy(tp->typ,p->typ,128); tp->count=1; tp->next=NULL; //printf("tag-typ %s\n",tp->typ); } else { while(tp->next!=NULL) { if(strncmp(p->tag,tp->tag,strlen(p->tag))==0)//match a exsiting tag { tp->count++; found=1; //printf("-------%s,%s---------\n",p->tag,tp->tag); break; } else { tp=tp->next; } } if((tp->next==NULL)&&strncmp(p->tag,tp->tag,strlen(p->tag))==0) { tp->count++; found=1; } if(found==0)//not found a matched tag { tp->next=malloc(sizeof(record_tag)); tp=tp->next; strcpy(tp->tag,p->tag); //printf("len= %d\n",strlen(p->typ)); strncpy(tp->typ,p->typ,128); tp->count=1; tp->next=NULL; //printf("tag-typ %s\n",tp->typ); } } return 0; } void dump_M_record(record_header *p) { char *buff=NULL; int is_a_time=0; double d; float f; short int s; int us; char *cd; int depth=0; time_t t; buff=p->typ + p->typ_len + RECORD_DAT_SIZE; depth=0; printf("\n%s\t",p->tag);// print tag before a record. int e=0; while(buff <= p->head+ p->size)// within the range of record size { printl("[e=%d,type=%x]",e,buff[0]); e++; switch(buff[0]) { case STRING://string, 0x09+string+NULL//HT buff++; if(strncmp(buff,"line",4)==0)//end of line. { printf("\n"); printf("%s\t**",p->tag);// print tag before each line. } else { printf(" %s ",buff); } if((strlen(buff))==1&&(strncmp(buff,"s",1)==0)) { is_a_time=1; } else { is_a_time=0; } buff+=strlen(buff)+1; //printf("%x",buff[0]); break; case DOUBLE://double , 0x08 + double 8 bit, Bigendian//BS //printf("%x,%x,%x,%x,%x,%x,%x,%x,%x\n",buff[0],buff[1],buff[2],buff[3],buff[4],buff[5],buff[6],buff[7],buff[8]); cd=(char*)&d; cd[0]=buff[8];cd[1]=buff[7];cd[2]=buff[6];cd[3]=buff[5]; cd[4]=buff[4];cd[5]=buff[3];cd[6]=buff[2];cd[7]=buff[1]; buff++; printf(" %g ",d); buff+=sizeof(double); //printf("after a float : %x\n",buff[0]); break; case FLOAT://double , 0x08 + double 8 bit, Bigendian//BS //printf("%x,%x,%x,%x,%x,%x,%x,%x,%x\n",buff[0],buff[1],buff[2],buff[3],buff[4],buff[5],buff[6],buff[7],buff[8]); cd=(char*)&f; cd[0]=buff[4];cd[1]=buff[3];cd[2]=buff[2];cd[3]=buff[1]; buff++; printf(" %g ",f); //#printf("FFFFFFFFFFFFFFFFFFFFFFFFF"); buff+=sizeof(float); break; //printf("after a float : %x\n",buff[0]); case ZERO://Char, just a zero buff++; printf(" %d ",0); //buff+=1; break; case ONE://Char, just a one buff++; printf(" %d ",1); //buff+=1; break; case CHAR://Char, 0x04+&#39;C&#39;+No NULL//EOT buff++; printf(" %d ",(unsigned char)buff[0]); buff+=1; break; case SHORT://Short int, 0x05 + short int (2 byte) + NO NULL//ENQ buff++; cd=(char*)&s; cd[0]=buff[1];cd[1]=buff[0]; printf(" %d ",s); buff+=2; break; case INT://Short int or ctime , 0x06 + int (4 byte) + NO NULL//ENQ buff++; if(is_a_time==1) { cd=(char*)&t; cd[0]=buff[3];cd[1]=buff[2];cd[2]=buff[1];cd[3]=buff[0]; printf(" %s ",ctime(&t)); } else { cd=(char*)&us; cd[0]=buff[3];cd[1]=buff[2];cd[2]=buff[1];cd[3]=buff[0]; printf(" %d ",us); } buff+=4; break; case CRA: case LFB:// End of line or end of sub line.//VT //printf("==>single 0b found at %d==>\n",buff-(p->head)); printf("\n"); printf("%s\t",p->tag); /* if(depth==0) { printf("{ "); } else printf("<");*/ depth++; buff++; //printf("depth=%d",depth); break; case N0:// End of line or end of sub line.//NULL buff++; depth--; if(depth<0) { buff = p->head + p->size+1; //printf("\n"); //printf("%s\t",p->tag); //printf("}\n"); } else { printf(" ");} //printf(">"); break; default: printf("unknown=%x ",(unsigned char)buff[0]); buff++; printf("reading failed on file: %s\n",filename); exit(0); //getch(); break; } } //p->body=t; } int handle_defined_record(record_header *p) { return 0; } int handle_LOT_record(record_header *p,char *xmlstr) { char * buff=NULL; char * end; char *cd; char temp[128]; //sprintf(xmlstr,"<lot_info>" int i; //char csv_file[128]; //char layout_file[]="..\\output\\wafer_layout.xml"; double d; unsigned int ui; buff=p->typ + p->typ_len + RECORD_DAT_SIZE; end=buff+(p->size); if(strncmp(LOT,p->tag,strlen(LOT))==0) { //printf("inside a %s\n",p->tag); sprintf(xmlstr,"<lot_info MDL_name=\"%s\" ",filename); buff+=next_value(buff,STRING,temp,end); while(strncmp(temp,"lot_id",strlen("lot_id"))!=0) { buff+=next_value(buff,STRING,temp,end); } buff+=next_value(buff,STRING,temp,end); sprintf(xmlstr,"%s lot_id=\"%s\" ",xmlstr,temp); buff+=next_value(buff,STRING,temp,end);//recipe id while(strncmp(temp,"recipe_id",strlen("recipe_id"))!=0) { buff+=next_value(buff,STRING,temp,end); } buff+=next_value(buff,STRING,temp,end); sprintf(xmlstr,"%s recipe_id=\"%s\" ",xmlstr,temp); buff+=next_value(buff,STRING,temp,end);//layer id while(strncmp(temp,"layer_id",strlen("layer_id"))!=0) { buff+=next_value(buff,STRING,temp,end); } buff+=next_value(buff,STRING,temp,end); sprintf(xmlstr,"%s layer_id=\"%s\" ",xmlstr,temp); while(strncmp(temp,"lot_size",strlen("lot_size"))!=0) { buff+=next_value(buff,STRING,temp,end); } if(buff[0]==ONE) { ui=1; } else { buff+=next_value(buff,CHAR,&ui,end);//layer id } printl("lot_size=%d",ui); sprintf(xmlstr,"%s lot_size=\"%d\"> </lot_info> ",xmlstr,ui); printd("lot xml=%s\n",xmlstr); return 1; } return 0; } int handle_IMAGE_record(record_header *p,char *xmlstr)// every WAM record is a single wafer. { char * buff=NULL; char * end; char *cd; char temp[128]; int i; int l; //char csv_file[128]; //char layout_file[]="..\\output\\wafer_layout.xml"; double d; float f; unsigned int ui; double w,h; buff=p->typ + p->typ_len + RECORD_DAT_SIZE; end=buff+(p->size); if(strncmp(IMAGE,p->tag,strlen(IMAGE))==0) { for(i=0;i<1;i++) { buff+=next_value(buff,STRING,temp,end); while(strncmp(temp,"image_size",strlen("image_size"))!=0) { l=next_value(buff,STRING,temp,end); if(l==-1)//end of record reached { printf("end of record before next\n"); break; } else { buff+=l; } } buff+=next_value(buff,DOUBLE,&d,end); w=d; buff+=next_value(buff,DOUBLE,&d,end); h=d; printf("w=%g,h=%g\n",w,h); if(w>0.00001) break; } sprintf(xmlstr,"<image_size w=\"%g\" h=\"%g\"> </image_size>\n",w,h); printd("image xml=%s",xmlstr); return 1; } return 0; } int handle_WAM_record(record_header *p,char *xmlstr)// every WAM record is a single wafer. { char * buff=NULL; char * end; char *cd; char temp[128]; //char csv_file[128]; //char layout_file[]="..\\output\\wafer_layout.xml"; double d; float f; unsigned int ui; wam_shot *shot_p=NULL; static record_WAM * wam=NULL; int i; static int wafer_no=0; //static FILE *flp=NULL; buff=p->typ + p->typ_len + RECORD_DAT_SIZE; end=buff+(p->size); if(strncmp(WAM,p->tag,strlen(WAM))==0) { buff+=next_value(buff,STRING,temp,end); printl("1st string= %s\n",temp); if(strncmp(temp,"Wafer Map is empty.",strlen("Wafer Map is empty."))==0) { printf("Wafer Map is empty. --> wafer =%d\n",wafer_no+1); return 0; } wafer_no++; printf("wafer no= %d\n",wafer_no); if(wam_set_head==NULL)//1st wafer and wam_set_head ==NULL { wam=malloc(sizeof(record_WAM)); // now the head point to the new element. wam_set_head=wam; wam->next=NULL; wam->shots=NULL; } else { wam->next=malloc(sizeof(record_WAM)); wam=wam->next; wam->next=NULL; wam->shots=NULL; } wam->wafer_no=wafer_no; //FILE *fp = NULL; //sprintf(csv_file,"..\\output\\wafer_%d.csv",wafer_no); //if((fp = fopen(csv_file, "wt+"))==NULL) //{ // printf("csv file create failed!\n"); // return 0; //} //wam->shots->nodes=NULL; while(strncmp(temp,"CHUCK_ID_",strlen("CHUCK_ID_"))!=0) { buff+=next_value(buff,STRING,temp,end); printl("%s\n",temp); } wam->chuck_id=temp[strlen("CHUCK_ID_")]-&#39;0&#39;; printd("-chuck_id==%d---",wam->chuck_id); buff+=next_value(buff,CHAR,&wam->max_nr_of_x_gridlines,end); printd("max_x = %d\n",wam->max_nr_of_x_gridlines); buff+=next_value(buff,STRING,temp,end); if(buff[0]==SHORT) buff+=next_value(buff,SHORT,&wam->max_nr_of_wams,end); else buff+=next_value(buff,CHAR,&wam->max_nr_of_wams,end); printd("max_wam = %d\n",wam->max_nr_of_wams); buff+=next_value(buff,STRING,temp,end); if(buff[0]==SHORT) buff+=next_value(buff,SHORT,&wam->nr_of_wams,end); else buff+=next_value(buff,CHAR,&wam->nr_of_wams,end); printd("nr_wam = %d\n",wam->nr_of_wams); //getch(); for(i=0;i<wam->max_nr_of_x_gridlines;i++) { buff+=next_value(buff,FLOAT,&f,end); printl("x[%d]=%g\n",i,f); wam->x_gridline_offsets[i]=f; } buff+=next_value(buff,STRING,temp,end);//"for wam printl("wams!!\n"); for(i=0;i<wam->nr_of_wams;i++) { // Todo: error here!! while(1) { buff+=next_value(buff,STRING,temp,end); printd("searching for id %s\n",temp); if(strncmp(temp,"id",strlen("id"))==0) { break; } } printd("a sid was found!, current count =%d, total shot count =%d \n",i+1,wam->nr_of_wams);//SID can be a Char or INT (if > 128) if(wam->shots==NULL)//1st shot { wam->shots=malloc(sizeof(wam_shot)); wam->shots->next=NULL; shot_p=wam->shots; } else { shot_p->next=malloc(sizeof(wam_shot)); shot_p=shot_p->next; shot_p->next=NULL; } shot_p->nodes=NULL; if(buff[0]==CHAR) { printd("looking a sid with char\n"); buff+=next_value(buff,CHAR,&shot_p->id,end); } else{ if(buff[0]==SHORT) { printd("looking a sid with short\n"); buff+=next_value(buff,SHORT,&shot_p->id,end); } else { if(buff[0]==ONE)//0x03 { buff++; shot_p->id=1; } else { printf("error!buff[0]=%x\n",buff[0]); //getch(); } } } buff+=next_value(buff,DOUBLE,&d,end); shot_p->cx=d; buff+=next_value(buff,DOUBLE,&d,end); shot_p->cy=d; printd("%d,%g,%g\n",shot_p->id,shot_p->cx,shot_p->cy); buff+=next_value(buff,FLOAT,&f,end); shot_p->x_gridlines_shift=f; buff+=next_value(buff,STRING,temp,end);//nr_of_x_gridlines printd("nr_of_x_gridlines = %s\n",temp); if(buff[0]==ONE) { shot_p->nr_of_x_gridlines=1; buff++; } else { buff+=next_value(buff,CHAR,&shot_p->nr_of_x_gridlines,end); } buff+=next_value(buff,STRING,temp,end);//nr_of_x_gridlines printd("nr_of_y_gridlines = %s\n",temp); if(buff[0]==ONE) { shot_p->nr_of_y_gridlines=1; buff++; } else { buff+=next_value(buff,CHAR,&shot_p->nr_of_y_gridlines,end); } printd("X=%d,y=%d\n",shot_p->nr_of_x_gridlines,shot_p->nr_of_y_gridlines); int j; for(j=0;j<shot_p->nr_of_x_gridlines;j++) { buff+=next_value(buff,FLOAT,&f,end); shot_p->x_gridline_offsets[j]=f; printl("x_grid[%d]=%g\n",j,f); } for(j=0;j<shot_p->nr_of_y_gridlines;j++) { buff+=next_value(buff,FLOAT,&f,end); shot_p->y_gridline_offsets[j]=f; printl("y_grid[%d]=%g\n",j,f); } //input data table int x,y; shot_p->nodes=malloc(sizeof(wam_node)*(shot_p->nr_of_x_gridlines+1)*(shot_p->nr_of_y_gridlines+1)); for(y=0;y<shot_p->nr_of_y_gridlines;y++) { buff+=next_value(buff,STRING,temp,end);//columns for(x=0;x<shot_p->nr_of_x_gridlines;x++) { buff+=next_value(buff,FLOAT,&f,end); shot_p->nodes[x+shot_p->nr_of_x_gridlines*y].z=f; printl("%d,%g,%g,%g\n",shot_p->id,\ shot_p->cx+shot_p->x_gridline_offsets[x]+shot_p->x_gridlines_shift,\ shot_p->cy+shot_p->y_gridline_offsets[y],\ f); } } //input valid table buff+=next_value(buff,STRING,temp,end);//valid for(y=0;y<shot_p->nr_of_y_gridlines;y++) { buff+=next_value(buff,STRING,temp,end);//columns for(x=0;x<shot_p->nr_of_x_gridlines;x++) { while(1) { buff+=next_value(buff,STRING,temp,end); //column printl("col=%s\n",temp); if(strncmp(temp,"FALSE",strlen("FALSE"))==0) { shot_p->nodes[x+shot_p->nr_of_x_gridlines*y].valid=0; break; } if(strncmp(temp,"TRUE",strlen("TRUE"))==0) { shot_p->nodes[x+shot_p->nr_of_x_gridlines*y].valid=1; break; } } if(shot_p->nodes[x+shot_p->nr_of_x_gridlines*y].valid==1) { /*fprintf(fp,"%d,%g,%g,%g\n",shot_p->id,\ shot_p->cx+shot_p->x_gridline_offsets[x]+shot_p->x_gridlines_shift,\ shot_p->cy+shot_p->y_gridline_offsets[y],\ shot_p->nodes[x+shot_p->nr_of_x_gridlines*y].z);*/ printl("%d,%g,%g,%g\n",shot_p->id,\ shot_p->cx+shot_p->x_gridline_offsets[x]+shot_p->x_gridlines_shift,\ shot_p->cy+shot_p->y_gridline_offsets[y],\ shot_p->nodes[x+shot_p->nr_of_x_gridlines*y].z); } } } printd("just finished matrix read!shot_p->id =%d \n",shot_p->id); }/* if(wafer_no==1){//write file header only 1st wafer and one single time //sprintf(layout_file,"..\\output\\wafer_layout_%d.xml",wafer_no); if((flp = fopen(layout_file, "wt+"))==NULL) { printf("layout xml file create failed!\n"); return 0; } fprintf(flp,"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); fprintf(flp,"<layout><wams>\n");//need </wams> </layout> //write wafer layout information to layout.csv files. } else// other wafer open just for append and no file header again. { if((flp = fopen(layout_file, "a"))==NULL) { printf("layout xml file reopen failed!\n"); return 0; } } */ //fprintf(flp,"%d,%d,%d\n",wam-,wam->chuck_id,wam->nr_of_wams); //free all wam,shot,nodes tree structure. /*while(wam->shots) { //struct wam_node *node_p=NULL; shot_p=wam->shots; if(wafer_no==1){//print only on 1st wafer //each line a single wam. fprintf(flp,\ "<wam \ id=\"%d\"\ cx=\"%g\" \ cy=\"%g\" \ nr_x_grid=\"%d\" \ nr_y_grid=\"%d\"> \ </wam>\n",shot_p->id,shot_p->cx,shot_p->cy,shot_p->nr_of_x_gridlines,shot_p->nr_of_y_gridlines); } wam->shots=wam->shots->next; if(shot_p->nodes) {free(shot_p->nodes);} free(shot_p); }*/ //print for every wafers //end wams here /*fprintf(flp,"</wams>\n <wafers> \ <wafer wafer_no=\"%d\" \ chuck_id=\"%d\"\ nr_of_wams=\"%d\"> \ </wafer>\n" ,wafer_no,wam->chuck_id,wam->nr_of_wams);//need </wafers> and </layout> fclose(flp); free(wam); fclose(fp); printd("wam freed and file closed!\n");*/ return 1; } else { return 0;//not handled } } int next_value(char *buff,int type,void *p,char * end) { char *cd; char * start=buff; int e; while(buff <= end)// within the range of record size { printl("[e=%d,type=%x]",e,buff[0]); e++; printl("looking for a %d\n",type); switch(buff[0]) { case STRING://string, 0x09+string+NULL//HT buff++; if(type==STRING) { strncpy(p,buff,128); //printf("%s\n",buff); buff+=strlen(buff)+1; return buff-start; } else { printd("looking for a %d, skip a STRING, buff=%s\n",type, buff); buff+=strlen(buff)+1; } break; case DOUBLE://double , 0x08 + double 8 byte, Bigendian//BS //printf("%x,%x,%x,%x,%x,%x,%x,%x,%x\n",buff[0],buff[1],buff[2],buff[3],buff[4],buff[5],buff[6],buff[7],buff[8]); if(type==DOUBLE) { cd=(char*)p; cd[0]=buff[8];cd[1]=buff[7];cd[2]=buff[6];cd[3]=buff[5]; cd[4]=buff[4];cd[5]=buff[3];cd[6]=buff[2];cd[7]=buff[1]; buff+=sizeof(double)+1; return buff-start; } else { printd("looking for a %d , skip a DOUBLE, buff=%x\n",type,buff[0]); buff+=sizeof(double)+1; } break; case FLOAT://float 0x07 like a double, but 4 bytes. if(type==FLOAT) { cd=(char*)p; cd[0]=buff[4];cd[1]=buff[3];cd[2]=buff[2];cd[3]=buff[1]; buff+=sizeof(float)+1; return buff-start; } else { printd("looking for a %d , skip a FLOAT, buff=%x\n",type,buff[0]); buff+=sizeof(float)+1; exit(1); } break; case ZERO://Char, 0x04+&#39;C&#39;+No NULL//STX printd("skip a ZERO, buff=%x\n",buff[0]); buff++; //printf(" %d ",0); //buff+=1; break; case ONE://Char, 0x04+&#39;C&#39;+No NULL//ETX printd("skip a ONE, buff=%x\n",buff[0]); buff++; //printf(" %d ",1); //buff+=1; break; case CHAR://Char, 0x04+&#39;C&#39;+No NULL//EOT buff++; //printf(" %d ",(unsigned char)buff[0]); if(type==CHAR) { *(unsigned int*)p=(unsigned int)buff[0]; buff++; return buff-start; } else { printd("skip a CHAR, buff=%x\n",buff[0]); buff++; } break; case SHORT://Short int, 0x05 + short int (2 byte) + NO NULL//ENQ buff++; if(type==SHORT) { cd=(char*)p; cd[0]=buff[1];cd[1]=buff[0]; //printf(" %d ",s); buff+=2; return buff-start; } else { printd("skip a SHORT, buff=%x\n",buff[0]); buff+=2; } break; case INT://Short int, 0x05 + short int (2 byte) + NO NULL//ENQ buff++; if(type==INT) { cd=(char*)p; cd[0]=buff[3];cd[1]=buff[2];cd[2]=buff[1];cd[3]=buff[0]; buff+=4; return buff-start; //printf(" %d ",us); } else { printd("skip a INT, buff=%x\n",buff[0]); buff+=4; } break; case CRA: case LFB:// End of line or end of sub line.//VT //printf("skip a CRLF, buff=%x\n",buff[0]); buff++; break; case N0:// End of line or end of sub line.//NULL //printf("skip a N0 , buff=%x\n",buff[0]); buff++; break; default: printf("unknown=%x ",(unsigned char)buff[0]); //getch(); buff++; printf("next_value failed on file: %s\n",filename); exit(0); break; } } return -1;//-1 means on finding within range OR wrong type requested. //p->body=t; } int get_wafer_maps(char * out_folder) { record_header * p=NULL; p=record_set_head;//link to head int find=0; char lot_xml[2048];//should be enough for a lot string. char image_xml[128]; char wafer_info_xml[2048];//[]="../output/wafer_info.xml"; char wafer_csv_file[2048]; FILE *fp_wafer_info = NULL; FILE *fp_wafer_csv=NULL; //printf(tag); if(out_folder[strlen(out_folder)]==&#39;/&#39;)//remove ending &#39;/&#39; if exist { out_folder[strlen(out_folder)]=&#39;\0&#39;; } sprintf(wafer_info_xml,"%s/wafer_info.xml",out_folder); printd("output to %s\n",out_folder); while(p!=NULL) { //parse_record(p);//parse the basic information of each record //printf("%s-->%s-->%d-->%d\n",tag,p->tag,strlen(tag),strlen(p->tag)); if(strncmp(WAM,p->tag,strlen(p->tag))==0) { handle_WAM_record(p,NULL); //continue; } if(strncmp(LOT,p->tag,strlen(p->tag))==0) { printf("%s,%s\n",LOT,p->tag); handle_LOT_record(p,lot_xml); //continue; } if(strncmp(IMAGE,p->tag,strlen(p->tag))==0) { //watch out , maybe there are two IMAGE record handle_IMAGE_record(p,image_xml); //continue; } p=p->next;// check next } //sprintf(csv_file,"..\\output\\wafer_%d.csv",wafer_no); if((fp_wafer_info = fopen(wafer_info_xml, "wt+"))==NULL) { printf("wafer_info_xml file create failed!\n"); return 0; } else { //write xml file header first fprintf(fp_wafer_info,"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); fprintf(fp_wafer_info,"<wafer_info> %s\n %s\n",lot_xml,image_xml); fprintf(fp_wafer_info,"<wams>\n"); record_WAM * wam_p=NULL; wam_p=wam_set_head; wam_shot *shot_p=wam_p->shots; while(shot_p!=NULL) { fprintf(fp_wafer_info,"<wam id=\"%d\" cx=\"%g\" cy=\"%g\" \ nr_of_x_gridlines=\"%d\" nr_of_y_gridlines=\"%d\">\ </wam>\n",\ shot_p->id,\ shot_p->cx,\ shot_p->cy,\ shot_p->nr_of_x_gridlines,\ shot_p->nr_of_y_gridlines); shot_p=shot_p->next; } fprintf(fp_wafer_info,"</wams>\n"); fprintf(fp_wafer_info,"<wafers>\n"); while(wam_p!=NULL) { fprintf(fp_wafer_info,"<wafer wafer_no=\"%d\" chuck_id=\"%d\" nr_of_wams=\"%d\"> </wafer>\n"\ ,wam_p->wafer_no,wam_p->chuck_id,wam_p->nr_of_wams); //sprintf(wafer_csv_file,"../output/wafer_%d.csv",wam_p->wafer_no); sprintf(wafer_csv_file,"%s/wafer_%d.csv",out_folder,wam_p->wafer_no); if((fp_wafer_csv = fopen(wafer_csv_file, "wt+"))==NULL) { printf("wafer_csv file create failed!\n"); return 0; } shot_p=wam_p->shots; int x,y; while(shot_p!=NULL){ for(y=0;y<shot_p->nr_of_y_gridlines;y++) { //buff+=next_value(buff,STRING,temp,end);//columns for(x=0;x<shot_p->nr_of_x_gridlines;x++) { if(shot_p->nodes[x+shot_p->nr_of_x_gridlines*y].valid==1) { fprintf(fp_wafer_csv,"%d,%g,%g,%g\n",shot_p->id,\ shot_p->cx+shot_p->x_gridline_offsets[x]+shot_p->x_gridlines_shift,\ shot_p->cy+shot_p->y_gridline_offsets[y],\ shot_p->nodes[x+shot_p->nr_of_x_gridlines*y].z); } } } shot_p=shot_p->next; } fclose(fp_wafer_csv); wam_p=wam_p->next; } fprintf(fp_wafer_info,"</wafers>\n"); fprintf(fp_wafer_info,"</wafer_info>\n"); fclose(fp_wafer_info); } //wam_shot *shot_p=NULL; free_wam(); return 1 ;// if no find , find =0; } /* typedef struct record_WAM { int wafer_no; int chuck_id; unsigned int max_nr_of_x_gridlines; unsigned int max_nr_of_wams; unsigned int nr_of_wams; double x_gridline_offsets[9]; struct wam_shot *shots; struct record_WAM * next; }record_WAM; */ int read_record(char * tag) { record_header * p=NULL; p=record_set_head;//link to head int find=0; //printf(tag); while(p!=NULL) { //parse_record(p);//parse the basic information of each record //printf("%s-->%s-->%d-->%d\n",tag,p->tag,strlen(tag),strlen(p->tag)); if(strncmp(tag,p->tag,strlen(p->tag))==0) { //printf("%s,%s,%c \n",p->tag,p->typ,p->fmt); if(handle_defined_record(p)==0) { dump_M_record(p); printf("\n++++++++++++++++++end of record+++++++++++++\n"); } //dump_M_record(p); find=1; } p=p->next;// check next } return find;// if no find , find =0; } void list_all_records() { record_tag * tp=NULL; tp=tag_set_head; int i=0; while(tp!=NULL) { printf("%4d\t%s\t%4d\t%s\n",i,tp->tag,tp->count,tp->typ); i++; tp=tp->next;// check next } } void read_all_records() { record_header * p=NULL; p=record_set_head;//link to head while(p!=NULL) { dump_M_record(p); p=p->next; } } int parse_file_body() { char * temp=NULL; int len_of_temp; int i; int len_of_buff; record_header * p=NULL; while((len_of_buff=read_to_buff())!=0) { temp=body_buff; len_of_temp=len_of_buff; printf("read count=%d\n",len_of_temp); //getch(); while(1)//len_of_temp > RECORD_MIN_SIZE) { if((temp=mystrstr(temp,RECORD_HEADER,len_of_temp,RECORD_HEAD_SIZE))!=NULL) { if(record_set_head==NULL)//1st record { if(record_set_head=malloc(sizeof(record_header))) { p=record_set_head; p->head=temp; p->next=NULL; } else { printf("malloc failed on head allocation!\n"); exit(0); } } else { if(p->next=malloc(sizeof(record_header))) { //printf("size of record =%4d\n",temp-(p->head)); p->size=temp -(p->head); //printf("size of current record = %d\n",p->size); p=p->next;//Update New record head p->next=NULL; p->head=temp; } else { printf("malloc failed on leaf allocation!\n"); exit(0); } } temp=temp+RECORD_MIN_SIZE; len_of_temp=len_of_buff-(temp-body_buff); if(len_of_temp <= RECORD_MIN_SIZE) { p->size = len_of_buff - (p->head - body_buff); break; } //printf("len_of_temp=%d\n",len_of_temp); } else//end of the buff { p->size = len_of_buff - (p->head - body_buff); //printf("size of last record = %d\n",p->size); break; } } }// we read each file in a whole p=record_set_head;//link to head while(p!=NULL) { parse_record(p);//parse the basic information of each record p=p->next; } if(feof(fp)) { printf("file end reached!\n"); return 1; } else { printf("read file_body failed!\n"); return 0; } } void free_wam(void) { wam_shot * shot_p=NULL; record_WAM * wam_p=NULL; int w=0,s=0; while(wam_set_head!=NULL) { wam_p=wam_set_head; wam_set_head=wam_set_head->next;//move to next wafer while(wam_p->shots!=NULL)//free every shot and move the pointer to next shot { shot_p=wam_p->shots; wam_p->shots=wam_p->shots->next; if(shot_p->nodes) { free(shot_p->nodes);//free nodes } free(shot_p);//free shots s++; } free(wam_p); w++; //free wafers } if(w>0){ printf("total %d wafers freed\n",w); printf("total %d shots freed\n",s*w);} //printf("wams all freed\n"); } void free_body(void) { record_header * p=NULL; record_tag *tp=NULL; int i=0; while(record_set_head!=NULL) { p=record_set_head; record_set_head=p->next; i++; // if(p->body) /// free(p->body); free(p); } printf("total %d records freed!\n",i); i=0; while(tag_set_head!=NULL) { tp=tag_set_head; tag_set_head=tp->next; i++; // if(p->body) // free(p->body); free(tp); } printf("total %d tags freed!\n",i); } int read_to_buff() { int len=0; if(fp&&!feof(fp)) { if(0==(len=fread(body_buff,1,BODY_BUFF_SIZE,fp))) { printf("Read body buffer failed!\n"); return 0; } else { if(len<sizeof(body_buff)) return len; else return sizeof(body_buff); } } else { printf("no file opened!\n"); return 0; } } void free_all(void) { free_body(); free_wam(); } //MAIN函数调用前面两个方法 int main(int argc, char* argv[]) { char *option=NULL; char buff[256];//for filename gzip cmd int i; if(argc > 2) { filename=argv[1]; if(strncmp(argv[1]+strlen(argv[1])-3,".gz",3)==0) { sprintf(buff,"gzip -d %s",argv[1]); i=system(buff); filename[strlen(argv[1])-3]=0; if(i==0) printf("%s decompressed, press a key to continue!%d\n",filename,i); else { printf("error on gzip\n"); //getch(); return 0;} } open_mdl_file(filename); if(read_file_header()==1) { printf("Read header OK!\n"); parse_file_body(); atexit(free_all); if(argv[2][0]==&#39;-&#39;)//-A= all -T==tag -L==list -I==interactive mode { switch(argv[2][1]) { case &#39;A&#39;: read_all_records(); break; case &#39;T&#39;: if(argc==4)// record tag as 4th parameter { printf("read of record %s\n",argv[3]); read_record(argv[3]); } else { printf("missing a tag name!\n"); } break; case &#39;I&#39;: printf("not ready yet,remind Robin!\n"); break; case &#39;L&#39;: printf("========List of records===========\n"); list_all_records(); break; case &#39;W&#39;://wafer map printf("========Get Wafer map data===========\n"); if(argc==4) { if(is_dir_exist(argv[3]))// record tag as 4th parameter { printf("write csv,xml to %s\n",argv[3]); get_wafer_maps(argv[3]); } else { printf("folder not find, will quit!\n"); } } else { printf("writing to default folder ./!\n"); get_wafer_maps("./"); } break; default: printf("wrong option,%c,select A,T,I,L !\n",argv[2][1]); break; } } else { printf("wrong option,%s,select -A,-T,-I,L !\n",argv[2]); } } else { printf("Read header failed!\n"); return 0; } close_mdl_file(); //free_body(); return 1; } else { printf("usage: read mdl_filename -A==all -T==tag -I==interactive mode -L==list\n "); return 0; } } 帮我检查我这次提供的这些文件中,除了C语言的标准库之外是否有没有被定义的模块。如果有请指出我会继续提供,如果没有那么帮我分析代码的结构和作用
06-10
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值