一、POSIX 正则库入门:从 说起
正则表达式,说白了就是一种模式匹配的语言。你给它一个字符串,它告诉你「这个字符串长啥样」。我刚开始接触 C 语言的正则时,第一反应是:标准库居然不直接支持?后来才发现,POSIX 标准早就把这事儿安排好了。
嗯,今天我们就来聊聊 POSIX 正则库。这套接口定义在
1.1 头文件:你需要的都在这里
打开
- regex_t:编译后的正则表达式结构体。你可以把它理解成一个「编译好的模式」。
- regmatch_t:匹配结果结构体。它记录了匹配到的子串在源字符串中的起始和结束位置。
- 四个核心函数:regcomp、regexec、regfree、regerror。
我个人习惯在写代码前先看一眼这些结构体的定义。比如 regex_t 里有个 re_nsub 字段,记录着正则表达式里有多少个捕获组。这个字段我在调试时经常用到。
核心要点:POSIX 正则库分为两套接口——基本正则表达式(BRE)和扩展正则表达式(ERE)。默认是 BRE,如果你想要更现代的语法(比如 +、?、|),记得在编译时加上 REG_EXTENDED 标志。
1.2 regcomp:编译正则表达式
写正则的第一步,是把字符串形式的模式「编译」成 regex_t 结构体。regcomp 就是干这个的。
#include <regex.h>
#include <stdio.h>
int main() {
regex_t regex;
const char *pattern = "^[a-zA-Z0-9]+@[a-zA-Z0-9]+\\.[a-zA-Z]{2,}$";
int ret;
ret = regcomp(®ex, pattern, REG_EXTENDED | REG_ICASE);
if (ret != 0) {
printf("编译失败\n");
return 1;
}
printf("编译成功!\n");
regfree(®ex);
return 0;
}
这里要注意几个点:
- 第三个参数是标志位:REG_EXTENDED 表示使用扩展正则语法,REG_ICASE 表示忽略大小写。你可以用 | 组合多个标志。
- 返回值:成功返回 0,失败返回非 0。别指望它返回正数表示成功——我见过有人写反了,调试了半天。
- 转义问题:C 字符串里,反斜杠本身需要转义。比如 \d 要写成 \\d。这个坑我踩过不止一次。
我的经验:如果你不确定正则语法对不对,先用 Python 或在线工具验证一下。C 语言调试正则太痛苦了,没有实时反馈。
1.3 regexec:匹配正则表达式
编译好了,就该上场了。regexec 负责在目标字符串里查找匹配。
#include <regex.h>
#include <stdio.h>
int main() {
regex_t regex;
const char *pattern = "hello";
const char *text = "hello world";
int ret;
regcomp(®ex, pattern, REG_EXTENDED);
ret = regexec(®ex, text, 0, NULL, 0);
if (ret == 0) {
printf("匹配成功!\n");
} else if (ret == REG_NOMATCH) {
printf("没有匹配\n");
} else {
printf("匹配出错\n");
}
regfree(®ex);
return 0;
}
参数说明:
- 第二个参数:目标字符串,必须是 null 结尾的 C 字符串。
- 第三、四个参数:regmatch_t 数组和它的长度。如果你只关心是否匹配,传 0 和 NULL 就行。
- 第五个参数:标志位,一般传 0。REG_NOTBOL 和 REG_NOTEOL 可以改变行首行尾的行为。
为什么会返回 REG_NOMATCH?说白了就是没找到。但如果你传了错误的 regex_t(比如没编译就拿来用),行为是未定义的。嗯,这里要注意:永远先编译,再匹配。
1.4 regfree:释放正则表达式
有借有还,再借不难。regcomp 内部会动态分配内存,用完必须释放。
regfree(®ex);
就这么一行。但很多人会忘记。我曾经在一个循环里反复编译正则而不释放,结果内存泄漏到程序崩溃。从那以后,我养成了「编译后立即配对 regfree」的习惯。
警告:regfree 之后,regex_t 结构体就失效了。不要再对它调用 regexec 或 regfree。如果你需要再次使用,必须重新 regcomp。
1.5 regerror:错误处理
regcomp 和 regexec 返回错误码时,你肯定想知道具体出了什么问题。regerror 就是干这个的。
#include <regex.h>
#include <stdio.h>
#include <string.h>
int main() {
regex_t regex;
const char *pattern = "[a-z"; // 故意写错
int ret;
char errbuf[256];
ret = regcomp(®ex, pattern, REG_EXTENDED);
if (ret != 0) {
regerror(ret, ®ex, errbuf, sizeof(errbuf));
printf("错误信息:%s\n", errbuf);
return 1;
}
regfree(®ex);
return 0;
}
参数说明:
- 第一个参数:错误码,就是 regcomp 或 regexec 的返回值。
- 第二个参数:对应的 regex_t 指针。如果错误发生在编译前,可以传 NULL。
- 第三、四个参数:输出缓冲区和大小。
我个人习惯把错误信息打印到 stderr,方便和正常输出分开。另外,errbuf 的大小建议至少 256 字节,有些错误信息挺长的。
1.6 知识体系总览
下面这张图帮你理清 POSIX 正则库的核心流程:
1.7 避坑指南
最后,分享几个我踩过的坑:
- 忘记释放:regcomp 分配的内存不会自动回收。程序退出时系统会回收,但长期运行的服务会泄漏。我曾经有一个后台服务跑了三天,内存从 10MB 涨到 2GB,最后发现是循环里忘了 regfree。
- 线程安全:regex_t 不是线程安全的。如果你在多线程环境里用同一个 regex_t 匹配,记得加锁,或者每个线程独立编译一份。
- 正则语法差异:POSIX 正则和 Perl 正则不一样。比如 \d 在 POSIX 里不支持,你得用 [[:digit:]]。这个坑我帮同事排查过,他死活想不通为什么 \d 匹配不到数字。
- 缓冲区溢出:regmatch_t 里的 rm_so 和 rm_eo 是下标,不是长度。计算子串长度时用 rm_eo - rm_so,别直接用 rm_eo。
我的习惯:写正则相关代码时,我会先写一个简单的测试函数,把各种边界情况都跑一遍。比如空字符串、超长字符串、特殊字符等。正则这东西,看着简单,用起来全是细节。
好了,POSIX 正则库的入门就聊到这里。四个函数,一个结构体,记住流程:编译 → 匹配 → 释放。出错时用 regerror 查原因。就这么简单。