gd32f103vbt6 串口OTA升级5-combin部分
一. 简介
本文主要是bin文件的组成进行一些简单介绍,方便理解升级的过程。

二.硬件部分
2.1 rk3399cpu+gd32f103
2.2 连接方式:串口(115200,8N1)或者iic(本文没有介绍iic)
三、其他需要说明的软件部分
3.1 单片机端分两个部分:iap(用于升级)和app(自己的应用)部分(这两个部分本文不做介绍)。
3.2 linux端做一个升级的app软件,这里称为update_app,本文主要是介绍该软件。
3.3 升级单片机用的bin文件,由iap的bin与app的bin的一个组合文件。由combin.exe在keil编译时调用完成。(本文不介绍该软件)
3.4 单片机flash的分区情况
3.4.1 0 ~(0x5c00-1) : iap程序区,用于存放iap程序
3.4.2 0x5c00~(0x6000-1) : 这个1k用于存放一些标志位,以及程序的md5
3.4.3 0x6000 ~(0x13000-1): app程序区,单片机的实际功能程序区
3.4.4 0x13000 ~ (0x20000-1) : app程序下载时的缓存区,程序下载时不直接下载到app区,而是先缓存到下载区,下载成功(数据完成后,会进行md5校验)后,才会更新到app区。
四、combin程序部分
4.1 main函数,程序需要三个参数
参数1: iap的bin文件名
参数2:app的bin文件名
参数3:合并后的文件名。本来准备设计为可以不要第三个参数的,此时传入NULL,但是没有测试。
int main(int argc,char* argv[])
{char* outfilename = NULL;if(argc < 3){printf("Usage : %s [outfilename]\n",argv[0]);return -1;}if(argc >= 4){outfilename = argv[3];}//第一个文件,与第二个文件合并,生成第三个文件,第三个可以不指定。combin_file(argv[1],argv[2],outfilename);return 0;}
4.2 主要的combin_file函数
4.2.1 两个bin文件打开后,都进行了头部的识别工作,非bin文件应该不能正常合并。
4.2.2 这里还考虑了通用单片机合并的情况,就是对偏移的值是进行读取,并不是固定值。
4.2.3 我使用的时候都是传入了参数3的,所以如何去组合出输出文件名没有测试,可能功能不正常。这里要考虑的问题是相对路径和绝对路径,取出文件名这些,好像有点复杂。
void combin_file(char* file1,char* file2,char* outfilename)
{FILE *fin1,*fin2;// *fout;int size1 = 0,size2 = 0,total_size = 0;char outnamebuf[64] = {0};uint16_t posision = 0; //位置int bw = 0; int readcount = 0;int ret,i;int lcd_inch = 0;if(file1== NULL || file2 == NULL){printf("ERROR : file is NULL \n");return ;}//1. 这一段是考虑没有指定输出文件名的情况,但是实际效果没有测试,可能有问题。//形成输出文件名if(outfilename == NULL){size1 = strlen(file1)-4;if(size1 > 29)size1 = 29;size2 = strlen(file2)-4;if(size2 > 29)size2 = 29;strncpy(outnamebuf,file1,size1); //不需要后缀strcat(outnamebuf,"_"); //加入下划线strncat(outnamebuf,file2,size2);strcat(outnamebuf,".bin");}else{size1 = strlen(outfilename);if(size1 > 63) //文件名太长了{size2 = size1;size1 = 59;} strncpy(outnamebuf,outfilename,size1);if(size2 > 63){strcat(outnamebuf,".bin");}} printf("outnamebuf = %s\n",outnamebuf);//2.得到app文件的md5值,存在全局变量中get_file_md5sum(file2);//3.打开iap的bin文件,全部读取处理fin1 = fopen(file1, "rb");if (fin1 != NULL){/* 文件打开成功*/printf("open %s success\r\n",file1);}else{printf("open %s error\r\n",file1);return ;}//4.打开app的bin文件,全部读取处理fin2 = fopen(file2, "rb");if (fin2 != NULL){/* 文件打开成功*/printf("open %s success\r\n",file2);}else{fclose(fin1);printf("open %s error\r\n",file2);return ;}//5.得到app文件的大小fseek(fin2, 0, SEEK_END);size2 = ftell(fin2);//6.得到iap文件的大小fseek(fin1, 0, SEEK_END);size1 = ftell(fin1);fseek(fin1, 0, SEEK_SET); printf("file2 size = %d\r\n", size2);total_size = size2+0x6000; //7.需要分配的空间的大小char* buf = malloc(total_size); //开一个空间if(buf == NULL){printf("error: total_size malloc %d\n",total_size);fclose(fin1);fclose(fin2);return ;}//8.因为flash中没有数据的位置都是0xff,所以把缓存填充0xffmemset(buf,0xff,total_size); //先填充0xff//9.读取appbin文件的内容,先判断前8个字节的内容,是否符合bin文件的特征。fseek(fin2, 0, SEEK_SET);ret = fread(buf, 1, 8, fin2); //从app文件中读取4个字节if(ret == 8){if (((*(uint32_t*)buf) & 0xfFFE0000 ) != 0x20000000){printf("image addr 0 != 0x20000000\n");printf("ERROR: bad image(app.bin)!!!!! combin cancle!!!,please check bin file!!!");fclose(fin1);fclose(fin2);free(buf);return ;}else if(((*(uint32_t*)(buf+4)) & 0xfFFf0000 ) != 0x08000000){printf("image addr %#x != ApplicationAddress %#x\n",((*(uint32_t*)(buf+4)) & 0xfFFffc00 ),0x08000000);printf("ERROR: bad image(app.bin)!!!!! combin cancle!!!,please check again!!!");fclose(fin1);fclose(fin2);free(buf);return ;}posision = ((*(uint16_t*)(buf+4)) & 0xfc00 ); //10.关键的一部,bin文件中可以知道app的偏移值,这里考虑的是不同单片机的偏移不同的问题printf("posision = %#x\n",posision);}else{printf("error: fread(buf, 1, 2, fin2)\n");fclose(fin1);fclose(fin2);free(buf);return;}//11.正常偏移是24k左右,但是综合考虑,选择了2k,怕有的程序的偏移过小。if(posision < 2048){printf("ERROR: bad app image(bin)!!!!! ,please check app.bin!!!\n");fclose(fin1);fclose(fin2);free(buf);return;}//12. iap的大小超过了整个偏移区的地址if(size1 > posision-1024){printf("ERROR: bad iap image(bin)!!!!! ,please check iap.bin!!!\n");fclose(fin1);fclose(fin2);free(buf);}//13.偏移不是24k的情况,调整一下大小if(posision <= 0x6000) //{total_size = total_size - 0x6000 + posision; //重新调整一下大小printf("total_size = %d\n",total_size);}else{printf("ERROR: bad app image(bin)!!!!! ,please check app.bin!!!\n");fclose(fin1);fclose(fin2);free(buf);return;}//14.文件2指针还原,指向文件开始的位置fseek(fin2, 0, SEEK_SET);//15.把iap的bin文件全部读出来printf("size1 = %d\n",size1);readcount = 0;do{bw = fread(buf+readcount, 1, size1-readcount, fin1);if(bw == 0)break;readcount += bw;} while (readcount < size1);//16.判断bin的合法性,if (((*(uint32_t*)buf) & 0xfFFE0000 ) != 0x20000000){printf("image addr 0 != 0x20000000\n");printf("ERROR: bad image(iap.bin)!!!!! combin cancle!!!,please check bin file!!!\n");fclose(fin1);fclose(fin2);free(buf);return ;}else if(((*(uint32_t*)(buf+4)) & 0xfFFffc00 ) != 0x08000000){printf("image addr %#x != ApplicationAddress %#x\n",((*(uint32_t*)(buf+4)) & 0xfFFffc00 ),0x08000000);printf("ERROR: bad image(iap.bin)!!!!! combin cancle!!!,please check again!!!\n");fclose(fin1);fclose(fin2);free(buf);return ;}//17.把app的bin文件全部读出来printf("size2 = %d\n",size2);readcount = posision; //app的位置,在bin中已经指定do{bw = fread(&buf[readcount], 1, size2, fin2);if(bw == 0)break;readcount += bw;} while (readcount-posision < size2);//18,缓存的特定位置加入下载标记,升级标记,文件大小和md5值posision -= 0x400; //退1k字节printf("posion2 = %#x\n",posision);(*(uint32_t*)(buf+4+posision)) = size2; //只保存app 的bin文件大小(*(uint16_t*)(buf+posision)) = 0xff; //不需要升级//19.这个地方比较特殊,设置对不同屏幕设置不同标记,方便单片机区分屏幕if(strstr(file2,"old5") != NULL){// lcd_inch = 4; (*(uint8_t*)(buf+posision-1)) = 5;printf("detect old5inch\n"); }else if(strstr(file2,"7inch") != NULL){// lcd_inch = 5; (*(uint8_t*)(buf+posision-1)) = 4; printf("detect 7inch\n"); }else if(strstr(file2,"new5") != NULL){// lcd_inch = 6;(*(uint8_t*)(buf+posision-1)) = 6; printf("detect new5inch\n"); }//20,缓存的特定位置md5值,后面以为要区分多个单片机的bin,所以不同的单片机进行了一些小的偏移//所以md5的位置也不会是固定的。同一个单片机肯定是固定在某个位置。posision += 512; //写入md5的值。2023-06-12 hj的+8if(strstr(file2,"hj22134") != NULL)posision += 8; // hj的+8printf("posion3 = %#x\n",posision);for(i=0;i<32;i++){buf[i+posision] = md5_readBuf[i];}fclose(fin1);fclose(fin2);//21.将缓存的内容生成第三个文件fin1 = fopen(outnamebuf, "wb");if (fin1 != NULL){/* 文件打开成功*/printf("3.open %s success \n",outnamebuf);}else{printf("3.open %s error \n",outnamebuf);free(buf);return ;}//22.写入文件readcount = 0;do{bw = fwrite(&buf[readcount], 1, total_size, fin1);readcount += bw;} while (readcount < total_size);fclose(fin1);free(buf);printf("combin complete!! size = %d\n",total_size);return;
}
五、总结
5.1 组合bin文件应该是一个比较简单的操作,但是说明一下,有助于理解升级过程。
5.2 该程序编译后,生成exe,由keil自动调用生成即可,比较方便。

点击编译之后,自动就生成好了。

5.3 全部代码如下:windows下使用mingw64编译即可
/*
* @Author: dazhi
* @Date: 2023-05-09 09:30:03
* @Last Modified by: dazhi
* @Last Modified time: 2023-07-19 14:17:56
*
* 如果使用md5sum的命令,则在windows下编译时,无法计算md值
* 下载mingw64:
* https://sourceforge.net/projects/mingw-w64/files/mingw-w64/mingw-w64-release/mingw-w64-v11.0.0.zip
* D:\Programfile\mingw64\bin\gcc combin.c -o combin
*/#include
#include
#include #include
#include
#include
//#include "md5.h"char md5_readBuf[64] = {0};#if 1 #define ROTATELEFT(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))/*** @desc: convert message and mes_bkp string into integer array and store them in w*/
static void md5_process_part1(uint32_t *w, unsigned char *message, uint32_t *pos, uint32_t mes_len, const unsigned char *mes_bkp)
{uint32_t i; // used in for loopfor(i = 0; i <= 15; i++){int32_t count = 0;while(*pos < mes_len && count <= 24){w[i] += (((uint32_t)message[*pos]) << count);(*pos)++;count += 8;}while(count <= 24){w[i] += (((uint32_t)mes_bkp[*pos - mes_len]) << count);(*pos)++;count += 8;}}
}/*** @desc: start encryption based on w*/
static void md5_process_part2(uint32_t abcd[4], uint32_t *w, const uint32_t k[64], const uint32_t s[64])
{uint32_t i; // used in for loopuint32_t a = abcd[0];uint32_t b = abcd[1];uint32_t c = abcd[2];uint32_t d = abcd[3];uint32_t f = 0;uint32_t g = 0;for(i = 0; i < 64; i++){if(i <= 15) //i >= 0 && {f = (b & c) | ((~b) & d);g = i;}else if(i >= 16 && i <= 31){f = (d & b) | ((~d) & c);g = (5 * i + 1) % 16;}else if(i >= 32 && i <= 47){f = b ^ c ^ d;g = (3 * i + 5) % 16;}else if(i >= 48 && i <= 63){f = c ^ (b | (~d));g = (7 * i) % 16;}uint32_t temp = d;d = c;c = b;b = ROTATELEFT((a + f + k[i] + w[g]), s[i]) + b;a = temp;}abcd[0] += a;abcd[1] += b;abcd[2] += c;abcd[3] += d;
}static const uint32_t k_table[]={0xd76aa478,0xe8c7b756,0x242070db,0xc1bdceee,0xf57c0faf,0x4787c62a,0xa8304613,0xfd469501,0x698098d8,0x8b44f7af,0xffff5bb1,0x895cd7be,0x6b901122,0xfd987193,0xa679438e,0x49b40821,0xf61e2562,0xc040b340,0x265e5a51,0xe9b6c7aa,0xd62f105d,0x02441453,0xd8a1e681,0xe7d3fbc8,0x21e1cde6,0xc33707d6,0xf4d50d87,0x455a14ed,0xa9e3e905,0xfcefa3f8,0x676f02d9,0x8d2a4c8a,0xfffa3942,0x8771f681,0x6d9d6122,0xfde5380c,0xa4beea44,0x4bdecfa9,0xf6bb4b60,0xbebfbc70,0x289b7ec6,0xeaa127fa,0xd4ef3085,0x04881d05,0xd9d4d039,0xe6db99e5,0x1fa27cf8,0xc4ac5665,0xf4292244,0x432aff97,0xab9423a7,0xfc93a039,0x655b59c3,0x8f0ccc92,0xffeff47d,0x85845dd1,0x6fa87e4f,0xfe2ce6e0,0xa3014314,0x4e0811a1,0xf7537e82,0xbd3af235,0x2ad7d2bb,0xeb86d391
};static const uint32_t s_table[]={7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21
};int32_t cal_md5(unsigned char *result, unsigned char *data, int length){if (result == NULL){return 1;}uint32_t w[16];uint32_t i; // used in for loopuint32_t mes_len = length;uint32_t looptimes = (mes_len + 8) / 64 + 1;uint32_t abcd[] = {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476};uint32_t pos = 0; // position pointer for messageuint32_t bkp_len = 64 * looptimes - mes_len; // 经过计算发现不超过72// unsigned char *bkp_mes = (unsigned char *)calloc(1, bkp_len);unsigned char bkp_mes[80];for(int i = 0; i < 80; i++) //初始化{bkp_mes[i] = 0;}bkp_mes[0] = (unsigned char)(0x80);uint64_t mes_bit_len = ((uint64_t)mes_len) * 8;for(i = 0; i < 8; i++){bkp_mes[bkp_len-i-1] = (unsigned char)((mes_bit_len & (0x00000000000000FF << (8 * (7 - i)))) >> (8 * (7 - i)));}for(i = 0; i < looptimes; i++){for(int j = 0; j < 16; j++) //初始化{w[j] = 0x00000000;}md5_process_part1(w, data, &pos, mes_len, bkp_mes); // compute wmd5_process_part2(abcd, w, k_table, s_table); // calculate md5 and store the result in abcd}for(int i = 0; i < 16; i++){//result[i] = ((unsigned char*)abcd)[i];sprintf((char*)result+i*2,"%02x",((unsigned char*)abcd)[i]); //2023-05-09 返回字符串}return 0;
}#endifint get_file_md5sum(const char * filename)
{FILE * fin1;
// char cmd[128] = {"md5sum "};int size1 ;int ret;int readcount,bw;fin1 = fopen(filename, "rb");if (fin1 != NULL){/* 文件打开成功*/printf("open %s success\r\n",filename);}else{printf("open %s error\r\n",filename);return 1;}fseek(fin1, 0, SEEK_END);size1 = ftell(fin1);fseek(fin1, 0, SEEK_SET); printf("file size = %d\r\n", size1);char* buf = malloc(size1); //开一个空间if(buf == NULL){printf("error: size1 malloc %d\n",size1);fclose(fin1);return 1;}readcount = 0;do{bw = fread(buf+readcount, 1, size1-readcount, fin1);if(bw == 0)break;readcount += bw;} while (readcount < size1);ret = cal_md5(md5_readBuf, buf, size1);//md5(buf, size1, md5_readBuf);if(!ret) printf("cal_md5 = %s\n",md5_readBuf);free(buf);fclose(fin1);
// strcat(cmd,filename);// filep = popen(cmd,"r");
// if(!filep)
// return -1;
// ret = fread(md5_readBuf,32,1,filep);// // printf("get_file_md5sum = %s\n",md5_readBuf);// pclose(filep);return ret;
}void combin_file(char* file1,char* file2,char* outfilename)
{FILE *fin1,*fin2;// *fout;int size1 = 0,size2 = 0,total_size = 0;char outnamebuf[64] = {0};uint16_t posision = 0; //位置int bw = 0; int readcount = 0;int ret,i;int lcd_inch = 0;if(file1== NULL || file2 == NULL){printf("ERROR : file is NULL \n");return ;}//1. 这一段是考虑没有指定输出文件名的情况,但是实际效果没有测试,可能有问题。//形成输出文件名if(outfilename == NULL){size1 = strlen(file1)-4;if(size1 > 29)size1 = 29;size2 = strlen(file2)-4;if(size2 > 29)size2 = 29;strncpy(outnamebuf,file1,size1); //不需要后缀strcat(outnamebuf,"_"); //加入下划线strncat(outnamebuf,file2,size2);strcat(outnamebuf,".bin");}else{size1 = strlen(outfilename);if(size1 > 63) //文件名太长了{size2 = size1;size1 = 59;} strncpy(outnamebuf,outfilename,size1);if(size2 > 63){strcat(outnamebuf,".bin");}} printf("outnamebuf = %s\n",outnamebuf);//2.得到app文件的md5值,存在全局变量中get_file_md5sum(file2);//3.打开iap的bin文件,全部读取处理fin1 = fopen(file1, "rb");if (fin1 != NULL){/* 文件打开成功*/printf("open %s success\r\n",file1);}else{printf("open %s error\r\n",file1);return ;}//4.打开app的bin文件,全部读取处理fin2 = fopen(file2, "rb");if (fin2 != NULL){/* 文件打开成功*/printf("open %s success\r\n",file2);}else{fclose(fin1);printf("open %s error\r\n",file2);return ;}//5.得到app文件的大小fseek(fin2, 0, SEEK_END);size2 = ftell(fin2);//6.得到iap文件的大小fseek(fin1, 0, SEEK_END);size1 = ftell(fin1);fseek(fin1, 0, SEEK_SET); printf("file2 size = %d\r\n", size2);total_size = size2+0x6000; //7.需要分配的空间的大小char* buf = malloc(total_size); //开一个空间if(buf == NULL){printf("error: total_size malloc %d\n",total_size);fclose(fin1);fclose(fin2);return ;}//8.因为flash中没有数据的位置都是0xff,所以把缓存填充0xffmemset(buf,0xff,total_size); //先填充0xff//9.读取appbin文件的内容,先判断前8个字节的内容,是否符合bin文件的特征。fseek(fin2, 0, SEEK_SET);ret = fread(buf, 1, 8, fin2); //从app文件中读取4个字节if(ret == 8){if (((*(uint32_t*)buf) & 0xfFFE0000 ) != 0x20000000){printf("image addr 0 != 0x20000000\n");printf("ERROR: bad image(app.bin)!!!!! combin cancle!!!,please check bin file!!!");fclose(fin1);fclose(fin2);free(buf);return ;}else if(((*(uint32_t*)(buf+4)) & 0xfFFf0000 ) != 0x08000000){printf("image addr %#x != ApplicationAddress %#x\n",((*(uint32_t*)(buf+4)) & 0xfFFffc00 ),0x08000000);printf("ERROR: bad image(app.bin)!!!!! combin cancle!!!,please check again!!!");fclose(fin1);fclose(fin2);free(buf);return ;}posision = ((*(uint16_t*)(buf+4)) & 0xfc00 ); //10.关键的一部,bin文件中可以知道app的偏移值,这里考虑的是不同单片机的偏移不同的问题printf("posision = %#x\n",posision);}else{printf("error: fread(buf, 1, 2, fin2)\n");fclose(fin1);fclose(fin2);free(buf);return;}//11.正常偏移是24k左右,但是综合考虑,选择了2k,怕有的程序的偏移过小。if(posision < 2048){printf("ERROR: bad app image(bin)!!!!! ,please check app.bin!!!\n");fclose(fin1);fclose(fin2);free(buf);return;}//12. iap的大小超过了整个偏移区的地址if(size1 > posision-1024){printf("ERROR: bad iap image(bin)!!!!! ,please check iap.bin!!!\n");fclose(fin1);fclose(fin2);free(buf);}//13.偏移不是24k的情况,调整一下大小if(posision <= 0x6000) //{total_size = total_size - 0x6000 + posision; //重新调整一下大小printf("total_size = %d\n",total_size);}else{printf("ERROR: bad app image(bin)!!!!! ,please check app.bin!!!\n");fclose(fin1);fclose(fin2);free(buf);return;}//14.文件2指针还原,指向文件开始的位置fseek(fin2, 0, SEEK_SET);//15.把iap的bin文件全部读出来printf("size1 = %d\n",size1);readcount = 0;do{bw = fread(buf+readcount, 1, size1-readcount, fin1);if(bw == 0)break;readcount += bw;} while (readcount < size1);//16.判断bin的合法性,if (((*(uint32_t*)buf) & 0xfFFE0000 ) != 0x20000000){printf("image addr 0 != 0x20000000\n");printf("ERROR: bad image(iap.bin)!!!!! combin cancle!!!,please check bin file!!!\n");fclose(fin1);fclose(fin2);free(buf);return ;}else if(((*(uint32_t*)(buf+4)) & 0xfFFffc00 ) != 0x08000000){printf("image addr %#x != ApplicationAddress %#x\n",((*(uint32_t*)(buf+4)) & 0xfFFffc00 ),0x08000000);printf("ERROR: bad image(iap.bin)!!!!! combin cancle!!!,please check again!!!\n");fclose(fin1);fclose(fin2);free(buf);return ;}//17.把app的bin文件全部读出来printf("size2 = %d\n",size2);readcount = posision; //app的位置,在bin中已经指定do{bw = fread(&buf[readcount], 1, size2, fin2);if(bw == 0)break;readcount += bw;} while (readcount-posision < size2);//18,缓存的特定位置加入下载标记,升级标记,文件大小和md5值posision -= 0x400; //退1k字节printf("posion2 = %#x\n",posision);(*(uint32_t*)(buf+4+posision)) = size2; //只保存app 的bin文件大小(*(uint16_t*)(buf+posision)) = 0xff; //不需要升级//19.这个地方比较特殊,设置对不同屏幕设置不同标记,方便单片机区分屏幕if(strstr(file2,"old5") != NULL){// lcd_inch = 4; (*(uint8_t*)(buf+posision-1)) = 5;printf("detect old5inch\n"); }else if(strstr(file2,"7inch") != NULL){// lcd_inch = 5; (*(uint8_t*)(buf+posision-1)) = 4; printf("detect 7inch\n"); }else if(strstr(file2,"new5") != NULL){// lcd_inch = 6;(*(uint8_t*)(buf+posision-1)) = 6; printf("detect new5inch\n"); }//20,缓存的特定位置md5值,后面以为要区分多个单片机的bin,所以不同的单片机进行了一些小的偏移//所以md5的位置也不会是固定的。同一个单片机肯定是固定在某个位置。posision += 512; //写入md5的值。2023-06-12 hj的+8if(strstr(file2,"hj22134") != NULL)posision += 8; // hj的+8printf("posion3 = %#x\n",posision);for(i=0;i<32;i++){buf[i+posision] = md5_readBuf[i];}fclose(fin1);fclose(fin2);//21.将缓存的内容生成第三个文件fin1 = fopen(outnamebuf, "wb");if (fin1 != NULL){/* 文件打开成功*/printf("3.open %s success \n",outnamebuf);}else{printf("3.open %s error \n",outnamebuf);free(buf);return ;}//22.写入文件readcount = 0;do{bw = fwrite(&buf[readcount], 1, total_size, fin1);readcount += bw;} while (readcount < total_size);fclose(fin1);free(buf);printf("combin complete!! size = %d\n",total_size);return;
}int main(int argc,char* argv[])
{char* outfilename = NULL;if(argc < 3){printf("Usage : %s [outfilename]\n",argv[0]);return -1;}if(argc >= 4){outfilename = argv[3];}//第一个文件,与第二个文件合并,生成第三个文件,第三个可以不指定。combin_file(argv[1],argv[2],outfilename);return 0;}
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
