如何用Perl解析C头文件?

7 投票
9 回答
7289 浏览
提问于 2025-04-15 12:15

我有一个头文件,其中包含一个很大的结构体。我需要用某个程序读取这个结构体,并对结构体的每个成员进行一些操作,然后再把它们写回去。

举个例子,我有一个这样的结构体:

const BYTE Some_Idx[] = {
4,7,10,15,17,19,24,29,
31,32,35,45,49,51,52,54,
55,58,60,64,65,66,67,69,
70,72,76,77,81,82,83,85,
88,93,94,95,97,99,102,103,
105,106,113,115,122,124,125,126,
129,131,137,139,140,149,151,152,
153,155,158,159,160,163,165,169,
174,175,181,182,183,189,190,193,
197,201,204,206,208,210,211,212,
213,214,215,217,218,219,220,223,
225,228,230,234,236,237,240,241,
242,247,249};

现在,我需要读取这个结构体,并对每个成员变量进行一些操作,然后创建一个顺序不同的新结构体,类似于:

const BYTE Some_Idx_Mod_mul_2[] = {
8,14,20, ...
...
484,494,498};

有没有现成的Perl库可以用来做这个?如果没有Perl,像Python这样的也可以。

有人能帮帮我吗!!!

9 个回答

4

你没有提供太多关于如何确定需要修改的内容的信息,不过我来解释一下你提到的具体例子:

$ perl -pi.bak -we'if ( /const BYTE Some_Idx/ .. /;/ ) { s/Some_Idx/Some_Idx_Mod_mul_2/g; s/(\d+)/$1 * 2/ge; }' header.h

我们来拆解一下,-p的意思是循环处理输入文件,把每一行放到$_里,执行提供的代码,然后打印$_。-i.bak则是让你可以直接在文件里编辑,同时把每个原始文件重命名,加上.bak后缀,并且把结果打印到一个新的文件,名字和原文件一样。-w是用来开启警告的。-e'....'是提供要对每一行执行的代码。header.h是唯一的输入文件。

在perl代码中,if ( /const BYTE Some_Idx/ .. /;/ )的意思是检查我们是否在一段行范围内,这段范围是从匹配/const BYTE Some_Idx/的行开始,到匹配/;/的行结束。

s/.../.../g是进行替换,尽可能多地替换。/(\d+)/是用来匹配一串数字的。/e标志表示结果($1 * 2)是要被计算的代码,而不是简单的替换字符串。$1就是要被替换的数字。

6

抱歉如果这个问题听起来很傻,但为什么要担心解析文件呢?为什么不写一个C语言程序,直接把头文件包含进来,按需要处理一下,然后输出修改后的头文件的源代码呢?我相信这样做会比用Perl或Python的解决方案简单得多,而且因为头文件是由C编译器来解析的,所以这样做会更可靠。

10

把数据放在头文件里会让用其他程序(比如Perl)来访问这些数据变得有点麻烦。你可以考虑另一种方法,把这些数据放在数据库里或者其他文件中,然后在需要的时候再生成头文件,甚至可以把这个过程放在你的构建系统里。这样做的原因是,生成C代码比解析C代码要简单得多。写一个脚本来解析文本文件并为你生成头文件是非常简单的,而且这个脚本可以从你的构建系统中调用。

假设你还是想把数据放在C头文件里,你需要以下两种方法中的一种来解决这个问题:

  • 一个快速的一次性脚本,能够准确(或者接近准确)地解析你描述的输入。
  • 一个通用的、写得很好的脚本,能够解析任意的C代码,并且可以在很多不同的头文件上工作。

在我看来,第一个情况似乎更常见,但从你的问题中很难判断是用一个需要解析任意C代码的脚本更好,还是用一个专门解析这个特定文件的脚本更好。对于处理你特定情况的代码,下面的代码对你的输入是有效的:

#!/usr/bin/perl -w

use strict;

open FILE, "<header.h" or die $!;
my @file = <FILE>;
close FILE or die $!;

my $in_block = 0;
my $regex = 'Some_Idx\[\]';
my $byte_line = '';
my @byte_entries;
foreach my $line (@file) {
    chomp $line;

    if ( $line =~ /$regex.*\{(.*)/ ) {
        $in_block = 1;
        my @digits = @{ match_digits($1) };
        push @digits, @byte_entries;
        next;
    }

    if ( $in_block ) {
        my @digits = @{ match_digits($line) };
        push @byte_entries, @digits;
    }

    if ( $line =~ /\}/ ) {
        $in_block = 0;
    }
}

print "const BYTE Some_Idx_Mod_mul_2[] = {\n";
print join ",", map { $_ * 2 } @byte_entries;
print "};\n";

sub match_digits {
    my $text = shift;
    my @digits;
    while ( $text =~ /(\d+),*/g ) {
        push @digits, $1;
    }

    return \@digits;
}

解析任意C代码有点复杂,对于很多应用来说并不值得,但也许你确实需要这样做。有一个小技巧是让GCC为你解析,并使用一个名为 GCC::TranslationUnit 的CPAN模块来读取GCC的解析树。假设你有一个名为test.c的单个文件,下面是编译代码的GCC命令:

gcc -fdump-translation-unit -c test.c

下面是读取解析树的Perl代码:

  use GCC::TranslationUnit;

  # echo '#include <stdio.h>' > stdio.c
  # gcc -fdump-translation-unit -c stdio.c
  $node = GCC::TranslationUnit::Parser->parsefile('stdio.c.tu')->root;

  # list every function/variable name
  while($node) {
    if($node->isa('GCC::Node::function_decl') or
       $node->isa('GCC::Node::var_decl')) {
      printf "%s declared in %s\n",
        $node->name->identifier, $node->source;
    }
  } continue {
    $node = $node->chain;
  }

撰写回答