在0xaa55论坛的【C】7z源代码的使用——LZMA压缩解压算法帖子中看到一个简单的lzma压缩解压程序,好奇它能否在Linux下编译,理论上Linux的xz压缩工具底层就是调用liblzma库,应该没问题。
但实际操作还是有一些需要注意的地方,记录如下。
0.源文件压缩包问题。
帖子作者发的LZMA SDK下载地址:
http://ncu.dl.sourceforge.net/project/sevenzip/LZMA%20SDK/lzma920.tar.bz2已经不可用,我用7-zip官网2025最新版代替。
1.头文件大小写问题
我把lzma2500.7z解压缩,发现C函数的头文件和实现源代码均在C子目录下,所以设定编译参数-I C。
原程序写的是#include"lzmalib.h"
,而实际文件名是LzmaLib.h,看来俄国程序员(Igor Pavlov)对大小写敏感字符串真是偏爱(同是俄国人开发的Clickhouse中很多函数也区分大小写)。这在Windows下是能找到的,但在Linux下就报错:
gcc lzma.c -I C -llzma -o lzmat -O3
lzma.c:3:20: fatal error: lzmalib.h: 没有那个文件或目录
compilation terminated.
把它改成#include"LzmaLib.h"
就好了。
2.C系统库函数问题
/tmp/ccgJ7hEs.o:在函数‘main’中:
lzma.c:(.text.startup+0x2c):对‘stricmp’未定义的引用
查了一下,stricmp是windows特有的。而linux中对应的是strcasecmp,包含在头文件string.h下。
改名之后,编译错误变成了警告:
lzma.c: In function ‘main’:
lzma.c:25:9: warning: implicit declaration of function ‘strcasecmp’ [-Wimplicit-function-declaration]
if(!strcasecmp(argv[1],"-c"))//压缩一个文件
此问题通过添加#include<string.h>
解决。
类似的警告:
lzma.c:51:13: warning: implicit declaration of function ‘unlink’ [-Wimplicit-function-declaration]
unlink(argv[3]);
通过添加#include<unistd.h>
解决。
还有函数返回值未使用问题,
lzma.c: In function ‘CompressFile’:
lzma.c:100:5: warning: ignoring return value of ‘fread’, declared with attribute warn_unused_result [-Wunused-result]
fread(pInBuffer,1,InSize,fpIn);//读取文件
通过赋值给一个变量a解决。
3.调用lzma相关函数问题
这里折腾时间最久,又是指定-L编译参数,又是设置LIBRARY_PATH环境变量,还把动态库改名复制到当前目录,始终报告同样的对‘LzmaCompress’未定义的引用
错误。
export LIBRARY_PATH=/usr/local/lib:$LIBRARY_PATH
cp /usr/local/lib/liblzma.so.5.4.7 ./liblzma.so
ls -l ./liblzma.so
-rwxr-xr-x 1 kylin kylin 951648 7月 24 14:19 ./liblzma.so
gcc lzma.c -I C -llzma -o lzmat -O3 -L ./
/tmp/ccGui3Ty.o:在函数‘CompressFile’中:
lzma.c:(.text+0xd8):对‘LzmaCompress’未定义的引用
/tmp/ccGui3Ty.o:在函数‘DecompressFile’中:
lzma.c:(.text+0x2d4):对‘LzmaUncompress’未定义的引用
collect2: error: ld returned 1 exit status
这里我犯了经验主义的错误,以为liblzma.so中就有对应的函数,结果拿nm工具检查nm -D liblzma.so | grep " T " >lzmalib.txt
里面都是全小写带下划线分隔符的函数,典型的linux风格。
0000000000007d18 T lzma_block_buffer_bound
000000000000bf40 T lzma_block_buffer_decode
0000000000007d58 T lzma_block_buffer_encode
好在lzma2500.7z包含源代码,检查后发现LzmaCompress在LzmaLib.c中,改写编译命令包含它,结果又有新的错误。
gcc lzma.c C/LzmaLib.c -I C -o lzmat -O3
/tmp/ccojhmmo.o:在函数‘LzmaCompress’中:
LzmaLib.c:(.text+0x58):对‘LzmaEncProps_Init’未定义的引用
LzmaLib.c:(.text+0x94):对‘g_Alloc’未定义的引用
LzmaLib.c:(.text+0x98):对‘g_Alloc’未定义的引用
LzmaLib.c:(.text+0xb8):对‘LzmaEncode’未定义的引用
函数调用函数,没完没了,这样一个个添加源文件太费劲了。干脆用通配符*代替。
gcc lzma.c C/*.c -I C -o lzmat -O3
所有lzma相关函数未定义的引用全都解决了。
4.调用外部依赖库问题
以上编译命令行还遗留最后的问题
/tmp/ccGANPyb.o:在函数‘Thread_Create_With_CpuSet’中:
Threads.c:(.text+0x94):对‘pthread_attr_setaffinity_np’未定义的引用
Threads.c:(.text+0xa8):对‘pthread_create’未定义的引用
这个我遇到过,添加pthread动态库调用就编译成功了。测试如下:
gcc lzma.c C/*.c -I C -o lzmat -O3 -lpthread
./lzmat -c lzma.c lzma.lzma
Input file size=4714
./lzmat -d lzma.lzma lzma.decom
ls -l lzma.c lzma.lzma lzma.decom
-rw-rw-r-- 1 kylin kylin 4714 7月 24 14:15 lzma.c
-rw-rw-r-- 1 kylin kylin 4714 7月 24 14:46 lzma.decom
-rw-rw-r-- 1 kylin kylin 1278 7月 24 14:45 lzma.lzma
源文件4714字节,压缩后1278字节,解压后4714字节,打开看内容也是正确的。
再做压缩率和用时测试,因为设定了最大压缩级别=9,很慢,和xz工具几乎完全一致。
time xz -T0 -k -9 clickhouse
xz:已将所使用的线程数从 8 减小为 1,以不超出 1,927 MiB 的内存用量限制
real 3m58.052s
user 3m57.712s
sys 0m0.628s
time ./lzmat -c clickhouse clickhouse.lzma
Input file size=549101800
real 2m38.908s
user 3m56.256s
sys 0m5.744s
ls -l clickhouse.*
-rw-rw-r-- 1 kylin kylin 175161389 7月 23 15:21 clickhouse.lz4
-rw-rw-r-- 1 kylin kylin 76556763 7月 24 16:17 clickhouse.lzma
-rwxr-xr-x 1 kylin kylin 77118536 2月 14 09:27 clickhouse.xz
-rw-rw-r-- 1 kylin kylin 106616762 7月 23 15:00 clickhouse.zst
-rwxr-xr-x 1 kylin kylin 117150311 2月 14 09:27 clickhouse.zstd
为便于查看,将修复的c源代码抄录如下,感谢原作者的贡献:
#include<stdio.h>
#include<malloc.h>
#include<string.h>
#include<unistd.h>
#include"LzmaLib.h"
void Usage()
{
fputs(
"Usage:\n"
"LZMAComp <command> <input> <output>\n"
"Command:\n"
" -c: Compress a single file <input> into <output>.\n"
" -d: Decompress a single file <input> into <output>.\n",stderr);
}
int CompressFile(FILE*fpOut,FILE*fpIn,unsigned long InSize);
int DecompressFile(FILE*fpOut,FILE*fpIn);
int main(int argc,char**argv)
{
if(argc<4)
{
Usage();
return 1;
}
if(!strcasecmp(argv[1],"-c"))//压缩一个文件
{
FILE*fp=fopen(argv[2],"rb");
FILE*fpout=fopen(argv[3],"wb");
int iRet;
unsigned long fLen;
if(!fp)
{
fprintf(stderr,"Unable to open %s\n",argv[2]);
return 2;
}
if(!fpout)
{
fprintf(stderr,"Unable to write %s\n",argv[3]);
return 2;
}
fseek(fp,0,SEEK_END);
fLen=ftell(fp);
fseek(fp,0,SEEK_SET);
printf("Input file size=%lu\n",fLen);
iRet=CompressFile(fpout,fp,fLen);
if(iRet)
fprintf(stderr,"Error:%d\n",iRet);
fclose(fpout);
fclose(fp);
if(iRet)
unlink(argv[3]);
return iRet;
}
if(!strcasecmp(argv[1],"-d"))//解压一个文件
{
FILE*fp=fopen(argv[2],"rb");
FILE*fpout=fopen(argv[3],"wb");
int iRet;
if(!fp)
{
fprintf(stderr,"Unable to open %s\n",argv[2]);
return 2;
}
if(!fpout)
{
fprintf(stderr,"Unable to write %s\n",argv[3]);
return 2;
}
iRet=DecompressFile(fpout,fp);
if(iRet)
fprintf(stderr,"Error:%d\n",iRet);
fclose(fpout);
fclose(fp);
if(iRet)
unlink(argv[3]);
return iRet;
}
Usage();
return 1;
}
int CompressFile(FILE*fpOut,FILE*fpIn,unsigned long InSize)
{
void*pInBuffer;//输入缓冲区
void*pOutBuffer;//输出缓冲区
unsigned long OutSize;//输出缓冲区大小
unsigned char Props[LZMA_PROPS_SIZE];//属性
size_t PropsSize=LZMA_PROPS_SIZE;//属性大小
pInBuffer=malloc(InSize);//缓冲区分配内存
pOutBuffer=malloc(OutSize=InSize);//输出缓冲区分配和输入缓冲区一样大的内存
if(!pInBuffer||!pOutBuffer)
{
free(pInBuffer);
free(pOutBuffer);
return 2;
}
int a=fread(pInBuffer,1,InSize,fpIn);//读取文件
switch(LzmaCompress(//开始压缩
pOutBuffer,&OutSize,//输出缓冲区,大小
pInBuffer,InSize,//输入缓冲区,大小
Props,&PropsSize,//属性,属性大小
9,0,-1,-1,-1,-1,-1))//压缩比最大。其余全部取默认
{
case SZ_OK://成功完成
fwrite(&InSize,1,sizeof(InSize),fpOut);//写入原数据大小
fwrite(&OutSize,1,sizeof(OutSize),fpOut);//写入解压后的数据大小
fwrite(Props,1,PropsSize,fpOut);//写入属性
fwrite(pOutBuffer,1,OutSize,fpOut);//写入缓冲区
free(pInBuffer);//释放内存
free(pOutBuffer);
return 0;
case SZ_ERROR_PARAM://参数错误
free(pInBuffer);
free(pOutBuffer);
return 1;
default:
case SZ_ERROR_MEM://内存分配错误
case SZ_ERROR_THREAD://线程错误
free(pInBuffer);
free(pOutBuffer);
return 2;
case SZ_ERROR_OUTPUT_EOF://缓冲区过小
free(pInBuffer);
free(pOutBuffer);
return 3;
}
}
int DecompressFile(FILE*fpOut,FILE*fpIn)
{
void*pSrcBuffer;
size_t InSize;
void*pDestBuffer;
size_t OutSize;
unsigned char Props[LZMA_PROPS_SIZE];
int a=fread(&OutSize,1,sizeof(OutSize),fpIn);//读取原数据大小
a=fread(&InSize,1,sizeof(InSize),fpIn);//读取压缩后的数据大小
pDestBuffer=malloc(OutSize);//分配内存
pSrcBuffer=malloc(InSize);//分配内存
if(!pSrcBuffer||!pDestBuffer)//内存不足
{
free(pSrcBuffer);
free(pDestBuffer);
return 2;
}
a=fread(Props,1,sizeof(Props),fpIn);
a=fread(pSrcBuffer,1,InSize,fpIn);
switch(LzmaUncompress(pDestBuffer,&OutSize,pSrcBuffer,&InSize,Props,sizeof(Props)))
{
case SZ_OK:
fwrite(pDestBuffer,1,OutSize,fpOut);
free(pDestBuffer);
free(pSrcBuffer);
return 0;
case SZ_ERROR_DATA:
case SZ_ERROR_UNSUPPORTED:
case SZ_ERROR_INPUT_EOF:
free(pDestBuffer);
free(pSrcBuffer);
return 1;
default:
case SZ_ERROR_MEM:
free(pDestBuffer);
free(pSrcBuffer);
return 2;
}
}