2.3 编译执行:从 ROOT macro 到独立程序
1. 运行方式
2.2 中的宏由 ROOT 的 Cling 即时编译。.L tracking.C+ 则使用 ACLiC 编译并加载宏。本节把同一算法编译为终端中可调用的程序,方便传入 run 号、批量处理文件。改变的是程序入口和文件管理,不是 tracking 方法。
2. 目录与 MakeClass
本例位于 code/compile1。main.cpp 管理输入输出,include/tracking.h 声明类,src/tracking.C 保存 2.2 的计算过程。生成 MakeClass 框架时不要覆盖已经修改的分析文件;可以在新目录中生成并比较。
Cling 和 ACLiC 已能执行编译后的 C++,不能简单认为“.x 是逐行解释、独立程序一定快很多”。独立程序的主要价值是明确入口、参数、依赖和退出状态,便于批量处理与维护。实际速度还取决于 I/O、事例循环和优化选项。
用 tree->MakeClass("tracking") 生成读取框架后,将头文件放进 include、实现文件放进 src。输入 Branch 改变时可重新生成框架,但先在单独目录中生成、比较差异,避免覆盖已经写好的分析代码。
%jsroot on
2. 主程序:参数、文件、分析调用
argc 是参数个数,argv[1] 是 run 号。没有提供目录时,输入在章目录,输出在本例程序目录。下面代码与实际 main.cpp 一致;读取失败时返回非零值,供批处理脚本判断。
#include <TFile.h>
#include <TTree.h>
#include <TString.h>
#include <cstdlib>
#include <iostream>
#include "tracking.h"
int main(int argc, char** argv) {
if (argc!=2 && argc!=4) {
std::cerr << "Usage: ./tracking run [input_dir output_dir]\n";
return 1;
}
char* end = nullptr;
long parsed = std::strtol(argv[1], &end, 10);
if (end==argv[1] || end!='\0' || parsed<0 || parsed>999999) {
std::cerr << "Invalid run number: " << argv[1] << '\n';
return 1;
}
int run = int(parsed);
const char inputDir = argc==4 ? argv[2] : "../..";
const char* outputDir = argc==4 ? argv[3] : ".";
TString inputName = Form("%s/f8ppac%03d.root",inputDir,run);
TString outputName = Form("%s/out%03d.root",outputDir,run);
TFile* input = TFile::Open(inputName);
if (!input || input->IsZombie()) {
std::cerr << "Cannot open " << inputName << '\n';
delete input;
return 1;
}
TTree* tin = input->Get<TTree>("tree");
if (!tin) {
std::cerr << "Missing tree in " << inputName << '\n';
delete input;
return 1;
}
TFile output(outputName,"RECREATE");
if (output.IsZombie()) return 1;
TTree* tout = new TTree("tree","PPAC tracking");
{
tracking analysis(tin);
analysis.Loop(tout);
std::cout << "Input=" << tin->GetEntries() << ", output=" << tout->GetEntries() << '\n';
output.Write();
} // MakeClass 基类析构时释放输入文件;不再重复 delete input。
return 0;
}
头文件:声明分析类
头文件告诉编译器“有哪些变量和函数”,源文件再给出函数如何计算。MakeClass 生成的 tracking.h 已包含输入 Branch 对应的成员,另加入 Loop(TTree *out) 等分析接口。头文件用 include guard 防止同一编译单元重复包含:
#ifndef TRACKING_H
#define TRACKING_H
// tracking 类的声明和成员变量
#endifguard 名称在工程内保持唯一。小型 inline 函数和模板定义可以放在头文件中,不是所有函数实现都禁止出现在头文件。
源文件:实现逐事件分析
src/tracking.C 先包含自己的头文件,用 tracking::Loop 实现类中声明的函数。循环依次读取事例、判断参考层是否有效、拟合径迹、计算靶点和待测层预测位置,最后填入输出树。它仍执行 2.2 的方法,改变的只是调用方式。
#include "tracking.h"
// 函数声明与实现的参数要保持一致:
void tracking::Loop(TTree *out) {
// 建立输出分支,读取事例并执行 2.2 的径迹计算。
// 完整实现见本页下方的 tracking.C。
}
3. Makefile:编译与链接
CXX = c++
CPPFLAGS = -Iinclude $(shell root-config --cflags)
CXXFLAGS = -O2 -Wall
LDLIBS = $(shell root-config --libs)
SOURCES = main.cpp $(wildcard src/*.cpp src/*.C)
HEADERS = $(wildcard include/*.h)
all: tracking
tracking: $(SOURCES) $(HEADERS)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(SOURCES) $(LDLIBS) -o $@
clean:
rm -f tracking
root-config --cflags 提供 ROOT 头文件及编译选项,--libs 提供链接库。包含头文件解决声明问题,链接解决函数实现问题,两者不能互相替代。修改源码后重新执行 make;编译失败先看第一条 error。
CXX 选择编译器,CPPFLAGS 包含头文件路径,CXXFLAGS 放优化与警告选项,LDLIBS 放链接库。$(wildcard src/.cpp src/.C) 收集源文件;$@ 表示当前目标 tracking。-Iinclude 使源码可以直接写 #include "tracking.h"。
make 根据依赖文件的修改时间决定是否重新编译。规则下面的命令以 Tab 开头。库参数放在对象或源文件之后,避免部分链接器按顺序解析时遗漏符号。
常见编译问题
“未声明”通常先检查头文件和命名空间,例如包含 <iostream> 并写 std::cout;“undefined reference”则检查是否编译了函数实现、是否链接了所需 ROOT 库。先修正第一条实质错误,再重新编译,后续报错可能只是它引出的连锁结果。
4. 运行和核对
在章目录执行下面两行。程序读取 f8ppac001.root,写出 code/compile1/out001.root。原始文件不变。使用同一输入与选择条件时,输出事例编号、靶点位置及拟合参数应与 2.2 一致,而不只是比较最终事例数。
!make -C code/compile1
!cd code/compile1 && ./tracking 1
make: Nothing to be done for `all'.
Processing Event: 30000 / 739685
Processing Event: 50000 / 739685
Processing Event: 110000 / 739685
Processing Event: 130000 / 739685
Processing Event: 140000 / 739685
Processing Event: 150000 / 739685
Processing Event: 190000 / 739685
Processing Event: 240000 / 739685
Processing Event: 270000 / 739685
Processing Event: 360000 / 739685
Processing Event: 390000 / 739685
Processing Event: 400000 / 739685
Processing Event: 410000 / 739685
Processing Event: 440000 / 739685
Processing Event: 450000 / 739685
Processing Event: 490000 / 739685
Processing Event: 550000 / 739685
Processing Event: 560000 / 739685
Processing Event: 590000 / 739685
Processing Event: 600000 / 739685
Processing Event: 620000 / 739685
Processing Event: 630000 / 739685
Processing Event: 640000 / 739685
Processing Event: 650000 / 739685
Input events=739685, accepted reference tracks=232180
Input=739685, output=232180
完整源码:main.cpp · tracking.h · tracking.C
