我有个很有趣的问题。
我想使用PCRE2和它的JIT函数。任务很简单:从文件中读取行并找到模式。
下面是示例代码:
#include <stdio.h>
#include <string.h>
#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>
int search(pcre2_code *re, unsigned char * subject) {
pcre2_match_data *match_data_real = pcre2_match_data_create_from_pattern(re, NULL);
size_t len_subject = strlen((const char *)subject);
int rc = pcre2_match(
re,
(PCRE2_SPTR)subject,
len_subject,
0,
0,
match_data_real,
NULL
);
pcre2_match_data_free(match_data_real);
return rc;
}
int main(int argc, char ** argv) {
unsigned char subject[][100] = {
"this is a foobar",
"this is a barfoo",
"this is a barbar",
"this is a foofoo"
};
pcre2_code *re;
PCRE2_SPTR pattern = (unsigned char *)"foo";
int errornumber;
PCRE2_SIZE erroroffset;
re = pcre2_compile(
pattern,
PCRE2_ZERO_TERMINATED,
0,
&errornumber,
&erroroffset,
NULL
);
pcre2_jit_compile(re, PCRE2_JIT_COMPLETE);
FILE *fp;
int s = 0;
while(s < 2) {
search(re, subject[s++]);
}
if (argc >= 2) {
fp = fopen(argv[1], "r");
if (fp != NULL) {
char tline[2048];
while(fgets(tline, 2048, fp) != NULL) {
search(re, (unsigned char *)tline);
}
fclose(fp);
}
}
pcre2_code_free(re);
return 0;
}
编译代码:
gcc -Wall -O2 -g pcretest.c -o pcretest -lpcre2-8
如您所见,在第58行中,我检查了是否有给定的参数,代码尝试将其作为文件打开。
另外,如第49行所示,我希望使用PCRE2的JIT。
这段代码也能正常工作,但我用Valgrind检查了一下,发现了一个有趣的行为:
- 如果我添加一个文件作为参数,那么Valgrind报告
Conditional jump or move depends on uninitialised value(s)
和Uninitialised value was created by a stack allocation
,但是它指向main()
。valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all --track-origins=yes -s ./pcretest myfile.txt
- 没有参数,就没有任何Valgrind报告。命令:
valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all --track-origins=yes -s ./pcretest
- 如果我在第55行注解掉
pcre2_jit_compile((*re), PCRE2_JIT_COMPLETE);
,那么一切都正常,没有任何Valgrind报告。命令:valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all --track-origins=yes -s ./pcretest myfile.txt
Valgrind的相关输出:
==31385== Conditional jump or move depends on uninitialised value(s)
==31385== at 0x4EECD1A: ???
==31385== by 0x1FFEFFFC1F: ???
==31385== Uninitialised value was created by a stack allocation
==31385== at 0x1090FA: main (pcretest.c:27)
...
==31385== HEAP SUMMARY:
==31385== in use at exit: 0 bytes in 0 blocks
==31385== total heap usage: 12 allocs, 12 frees, 13,486 bytes allocated
==31385==
==31385== All heap blocks were freed -- no leaks are possible
==31385==
==31385== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
==31385==
==31385== 1 errors in context 1 of 1:
==31385== Conditional jump or move depends on uninitialised value(s)
==31385== at 0x4EECD1A: ???
==31385== by 0x1FFEFFFC1F: ???
==31385== Uninitialised value was created by a stack allocation
==31385== at 0x1090FA: main (pcretest.c:27)
==31385==
==31385== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
第27行是int main(...)
。
我错过了什么?
1条答案
按热度按时间xwbd5t1u1#
意见:
main()
的初始调用的堆栈帧中。main()
。当然,显然,错误与PCRE2的JIT编译器生成的机器码有关。如果您不执行JIT编译,则通过普通匹配路径获得正确的操作。如果您 * 执行 * JIT编译,则使用JIT生成的代码,并且该代码触发Valgrind错误。尽管如此,您仍可能获得正确的匹配。但对于触发Valgrind错误的代码,我不会依赖于此。
我仔细研究了您代码的变体,发现错误与函数
search()
中对pcre2_match_data_create_from_pattern()
和pcre2_match()
的调用有关。任何一个都会导致Valgrind报告错误。但是为什么错误只发生在对search()
的某些调用中?这似乎是因为JIT编译在
main()
的堆栈帧中设置了数据结构,而这些结构在执行if (argc > 2)
语句的主体时被破坏了,这与我通过在该块中添加变量tline
的初始化器来避免错误的事实是一致的:我可以想象出各种各样的场景来说明为什么这会产生不同的效果,所有这些场景都与JIT生成的代码和编译器生成的代码如何操作堆栈指针有关。
就我个人而言,发现这样的问题可能会说服我远离PCRE的JIT编译器。至少在我有证据表明模式匹配是我的程序的性能热点之前,我肯定会这样做。但是,如果你必须使用JIT,那么这里有一些建议,可能会(也可能不会)帮助你避免麻烦:
1.把“及时”放在心上:尽可能接近实际使用模式的时间执行JIT。
1.不要假设JIT代码是长期可行的。特别是,在调用JIT编译器的函数返回之后使用它可能是不安全的,但即使是那么长时间,它也可能不好。
1.(仅)在运行JIT编译器的同一函数中使用JIT编译的正则表达式。
1.使该功能尽可能简单。
1.在函数开始时声明该函数的所有局部变量,并使用初始化式。
1.彻底测试。
对于您的特定示例代码来说,这似乎不是解决问题所必需的,但它的目的更普遍地是减少编译后的程序违反JIT所做假设的横截面。