c++ /Mavericks最佳实践

本指南主要介绍如何调整Bioconductor包中的C/ c++代码以构建在Mac OS X 10.9 (Mavericks)上。如果您的包不使用C或c++代码,则可以假定此文档与您无关。

目录

本文档除了最后一部分外,其余部分都要从头读到尾。

你可以跳到教训了部分来检查您的问题是否已经研究过。

取向

注意:为了简单起见,本指南使用“GCC”(GNU Compiler Collection)和“clang”来指代每个工具集合(包括c++编译器),而不是简单地使用“GCC C编译器”或“clang C编译器”。“Mavericks环境”指的是clang和Xcode版本的组合,默认情况下Mavericks可用。

随着R的Mavericks版本的发布,CRAN和Bioconductor已经采用了苹果的首选工具链来为Mavericks平台构建软件包。Bioconductor包构建在Mavericks平台上,使用OS X默认的组合铿锵声而且Xcode

Mavericks环境的引入揭示了构建包的许多问题,其中大部分是由于C/ c++代码过于依赖GCC的做事方式。过渡到Mavericks所揭示的大多数问题都是由c++编码实践引起的,这些实践被普遍认为是有问题的,并通过坚持既定的方法来解决最佳实践

以下是一些常见的问题来源,以Mavericks环境与GCC环境的对比为例:

在小牛的环境下发展有什么不同?

最大的变化是引入了clang 'slibc + +c++ 11标准库的实现以及与Xcode相关的库头。clang获得市场份额有几个原因,其中一个原因是clang仅被设计为基于c的语言的编译器。铿锵的支持者认为:

有关开发时使用的编译器标志的指导,请参见包装指南页面的相关部分

未指定行为、内存寻址策略的差异

在转换过程中遇到的一些错误似乎可以归因于对不可移植的未指定行为的依赖。请参阅本指南的有关章节未指明的行为获取更多信息。

例如,C/ c++内存寻址的许多方面是依赖于实现的,这意味着预期的行为不是由C/ c++标准规定的(“未指定”),因此由编译器编写者决定。

关于内存最重要的区别是clang似乎对越界内存寻址更有限制。

如何发现漏洞

对于GCC,首选的调试器是广东发展银行,但许多人更喜欢lldb在Mavericks环境中进行调试。支持其他平台上的LLDB目前是有限的。

由于包中的许多错误都与内存寻址或布局错误有关,因此仅依靠调试器可能不足以跟踪内存错误。Valgrind是检测内存错误的主要工具。

请参阅上的Bioconductor指南调试C/ c++代码关于使用调试器和Valgrind的例子。

如果我不能进入小牛队的机器怎么办?

使用Mavericks机器对在Mavericks上失败的包进行故障排除是无可替代的。我们看到的许多错误只有在clang、Xcode和OS X 10.9的组合下才能重现。

但是除了购买小牛的机器,还有几个选择:

  1. 作为一个勘探测量,安装最新版本的GCC并使用它编译包化c++ 11化gnu c++ 11编译器参数(请参阅计划的指导方针有关开发编译器标志的信息);的版本4.8.1GCC实现了2011年ISO c++标准的所有主要特性。对于错误和警告的诊断在最近的GCC版本中也有了很大的改进。使用一个c++ 11实现可能会显示警告或错误,这些警告或错误指向在Mavericks上遇到的相同问题。

  2. 安装铿锵声;这是有限的价值,因为许多错误是独行侠环境特有的。

  3. 使用Valgrind解决内存问题;因为Mavericks平台上的许多错误都与内存寻址问题有关,所以在Linux上使用Valgrind也可以发现许多错误。

  4. 的组合,如果无法诊断问题构建系统输出和Valgrind,请随时联系Bioc-devel邮件列表

c++ 11

尽管Mavericks上clang的默认版本包括对所有c++ 11特性的支持,但Bioconductor对c++ 11的支持依赖于拥有最古老工具链的平台。由于当前的Snow Leopard (Mac OS X 10.6.8)工具链不支持任何c++ 11特性,Bioconductor包通常不应该使用c++ 11特性。最终,当Mavericks被更广泛地采用时,对雪豹的支持将会被放弃。

c++ 11完全向后兼容旧标准。

可以告诉clang使用标准库的旧版本(默认为libc + +),但依赖于特定于OS版本的编译设置并不是一个可行的长期解决方案。这种方法极大地增加了包作者的维护负担,并限制了Bioconductor团队提供支持的能力。

应该调整代码以避免向后或向前不兼容的结构。看到forward-incompatibility问题部分提供示例。

小牛环境特有的问题

C链接

c++使用外来的“C”为声明提供C链接,从而使声明可被C代码访问。一些R头的时候# includec++中的d将包含应该包含的c++系统头文件有C连杆。根据相关编写R扩展手册部分R头文件应该包括在外来的“C”块。

链接不良的典型症状是在包装上加载时间(不是编译或链接时)一个错误说一个特定的符号找不到。

解决方案:所有R头文件应该是# included外来的“C”块。

正确的例子# includeR个报头:

#include  extern "C" {void foo();//函数'foo'和这个块中的其他代码有C链接…} extern "C" void bar();//函数bar有C链接

OpenMP

在撰写本文时,Mavericks环境还不支持OpenMP,苹果发布的工具是否会支持还是个未知数。

代码不应该依赖于OpenMP的可用性。与OpenMP支持无关,代码应该从一开始就在单线程环境中很好地降级。

看到编写R扩展手册部分有关R包中的OpenMP代码的信息,以及检测支持。

解决方案:使用预处理器if-else指令,如果OpenMP支持不可用,代码会优雅地降级:

#ifdef SUPPORT_OPENMP //多线程OpenMP版本的代码#else //单线程版本的代码#endif

看到ShortRead包提供了支持OpenMP的良好实践示例。

c++最佳实践

这些c++实践适用于大多数c++项目,但是Bioconductor社区认为它们特别有助于避免Mavericks环境中的问题。

使用Rcpp

RcppCRAN包允许无缝集成c++与R,并且是跨平台的。该一揽子计划为美国政府提供了许多相同的好处RC接口,使c++作为一门语言如此吸引人,同时消除了编程的许多陷阱R接口。

该包有良好的文档,并且有一个用于许多任务的工作示例的广泛存储库Rcpp画廊

避免名称解析错误

当编译器遇到一个不明确的标识符(例如,变量或函数名)(即两个或多个标识符之间存在“冲突”),或者名称查找规则导致编译器错误地解析一个名称时,就会发生名称解析错误。Clang对标识符更加严格。

名称解析错误的一个典型症状是编译器报错函数所获得的参数类型或数量与它所期望的不同,并且编译器指向标准库中的c++头文件。

在编写R包时,关于名称解析有两个主要问题:

从R报头重新映射标识符

为了方便起见,R的通用标识符别名R头。例如,Rf_length(饱和度指数)就变成了长度(饱和度指数).虽然这可能很方便C在Mavericks环境中,头文件的组织似乎比GCC环境中更容易发生冲突。参见相关章节编写R扩展手册

解决方法:防止重映射R通过定义R_NO_REMAP符号来定义c++代码的标识符。这可以在包级别使用-DR_NO_REMAP预处理器标志来完成,也可以在逐个文件的基础上使用#定义R_NO_REMAP.的完全限定版本R标识符,通常通过前置Rf_

防止重映射的头文件示例摘录:

文件CxxCode.h ------------------ #ifndef CXX_CODE_H #define CXX_CODE_H #ifdef __cplusplus #define R_NO_REMAP #endif…void foo(SEXP s) {if(Rf_length(s) > 1) //完全限定:'Rf_length'…} # endif

请注意Rf_length这只是众多例子中的一个吗R可能与c++标准库头中的名称冲突的标识符。

名称空间的卫生

c++中引入名称空间是为了限制名称冲突的发生。然而,许多刚接触c++的作者使用using引用(特别是“使用命名空间STD'指令),从而重新引入了名称空间本来要解决的问题。

正如在cppreference注释部分的使用使用命名空间STD指令为名称解析引入了整个STD名称空间。在标准库中的所有标头中,很可能有一个标识符与包中的标识符冲突。

解决方案:避免使用“使用命名空间STD'指令,如果可能的话,特别是在头文件中。更喜欢使用- - - - - -声明过度使用指令或简单地使用标准库标识符的完全限定版本。新的c++作者高估了包含范围解析操作符(即'std::’)会影响可读性。

引入std命名空间标识符的示例:

//假设我们想访问std::map和std::make_pair标识符

许多新的c++作者将使用using指令来引入他们需要的标识符。避免如果可能的话:

使用命名空间std;//引入整个STD命名空间进行解析

一种替代方法是使用-declarations(例如,'使用std::地图;',它允许手动选择要引入的标识符(而不是整个STD命名空间);这里我们只需要std::地图而且std:: make_pair.即使我们想要的标识符列表很长,我们也只需要为每个标识符添加一个using-declaration即可:

使用std::地图;//使用std::make_pair在声明范围内引入'map'和'make_pair';

使用声明也可以是块范围的。这比在全局范围内使用-declarations更可取,因为它可以防止在全局范围内不必要地引入名称,这是良好的命名空间卫生原则:

Void foo(){使用std::map;使用std:: make_pair;Map  m;m.insert (make_pair(5、7));...}

一个完美的替代方法是简单地在标准库标识符前面加上'std::,大多数c++程序员都习惯阅读:

Void foo() {std::map m;m.insert (std:: make_pair(5、7));}

避免未定义行为和不可移植的未指定行为

C或c++标准中没有规定的行为有两大类:

有问题的未定义或未指定行为的典型症状是只出现在Mavericks环境中的段错误。之前没有发现问题的原因可能是GCC静默地允许代码执行,而不是使程序崩溃。

解决方案:防御性地编写代码以避免有问题的结构和使用调试器找出导致错误的代码。

来自外部源代码的代码的问题

有些包需要使用不是由贡献者直接编写的代码。最常见的情况是包含第三方编写的库的源代码。有些包还使用代码生成工具生成的代码,例如:痛饮.首先,查看相关信息包装指引部分获取外部源代码的代码指导。

看到经验教训节中有关特定代码源的建议。

生成的代码

有些包使用由第三方工具生成的代码,即由机器编写的代码。SWIG就是一个常见的例子。

由机器编写的代码的一个问题是,代码的目的是由机器。例如,SWIG生成的每个代码文件的顶部都声明代码不能手工阅读或编辑。

因为许多代码生成工具所做的假设对于Mavericks环境是无效的,所以代码需要人力来修复错误;但是由于机器编写的代码具有不可理解的性质,因此很难将错误隔离出来。

解决方案:如果可能,重新生成问题代码,否则手工修复。手工修理是强烈的打击

第三方代码

有些包包含的第三方库不是以独立于编译器的方式编写的,因此不能在Mavericks环境中构建开箱即用的库。

近似优先顺序的解:

  1. 检查是否存在凹口或Bioconductor包提供相同的功能,同时满足您的用例的性能需求。从包中消除第三方代码可以极大地减少维护负担。

  2. 检查库是否已更新。一些拥有活跃用户社区的库会进行更新,增加对更多编译器/环境的支持。

  3. 检查维护者是否意识到该库不适用于Mavericks环境,并确定是否即将提供支持。通常很容易直接联系由个人或小组维护的库的作者。

  4. 使用主动维护的、提供等效功能的备选库。有时,如果一个库不再维护,这是因为该库已被提供相同功能的替代项目所放弃。

  5. 手动更新包中包含的库代码。强烈的打击.维护人员负责保持代码与主线源项目保持一致。如果需要,请记录所需更改的描述,以便在代码库更新时可以轻松地再现更改。

从具体问题中吸取的教训

本节作为一个组织松散的知识库,用于存储关于特定问题及其解决方案的知识。预计它不会是全面的。项目将随着知识库的增长而添加。Bioconductor渴望得到建议;请填写Bioc-devel邮件列表如果你有的话!

如果您不知道从哪里开始诊断您的坏包,那么浏览本节的所有内容可能是值得的。

在相关的地方,问题被标记为在编译时或运行时可发现。

在适用的地方,提供了一个到实时代码演示的链接。

c++ 11的前向不兼容问题

c++ 11不是完全向后兼容的。特别是,标准库的某些部分的API发生了轻微的变化,可能是微妙的变化。大多数问题只需要一点点调整就能解决。

容器迭代器常量

类型:编译时

标准库容器上的许多操作现在要求迭代器为常量.两个现成的例子是插入而且擦除接受迭代器参数的方法。

迭代标准库容器

类型:运行时

一般来说,使用特殊结束之后迭代器的值,而不是相等性检查(即,= =! =)会导致未定义的行为。尤其是在小牛的环境下:

下面是增量超越的演练示例结束之后,请参阅diagnose-a-crash例子在调试C/ c++页面。

外部代码源

外部代码源的常见示例如下痛饮提高,以及许多文件格式库。来自外部源代码的代码有时以非编译器独立的方式编写。检查文档,查看是否支持Mavericks环境。

提高

提高是一个免费的、经过同行评审的c++库的来源,这些库增强了该语言。Boost的许多部分是“仅头文件”的,这意味着它们不需要单独编译,头文件只需要出现在搜索路径中,以便客户端代码使用它们。

许多Boost库是平台独立的,但不是全部。一些Boost库要么正在添加Mavericks环境支持,要么库作者已经宣布将支持Mavericks环境被添加。

近似优先顺序的解:

  1. 使用黑洞如果可能,在CRAN上进行包装。BH包提供了几个Boost纯头库。使用BH包意味着在包中使用Boost的维护成本几乎为零。

  2. 更新包中包含的Boost库。Boost库有时包含bug,或者稍后更新以添加对其他平台的支持。更新包中的所有代码是Bioconductor包维护者的责任。

  3. 联系特定Boost库的作者。如果您找不到关于支持Mavericks环境的公告,可能值得联系库作者进行查询。

痛饮

SWIG生成代码,用于在用C/ c++编写的代码与其他语言编写的代码之间进行交互。在撰写本文时,SWIG对clang的支持是有限的,而且SWIG在clang的libc++版本(c++ 11)标准库方面尤其存在问题。有些问题仅限于可以通过调整函数签名来解决的问题。其他问题深深嵌入在SWIG生成代码的方式中。

写这篇文章的时候,这个线程在SWIG-devel邮件列表中似乎对与SWIG在小牛的合作进行了最深入的讨论。

近似优先顺序的解:

  1. 如果可能,消除SWIG代码。这可能对减少维护负担最有效。

  2. 使用最新版本的SWIG重新生成SWIG代码。在撰写本文时,SWIG最近进行了更新,以包括对c++ 11的部分支持,这可能会缓解clang的问题libc + +.看到SWIG文档关于c++ 11的支持.新版本可能不会产生有问题的代码。注释代码必须对所有受支持的编译器有效。

  3. 手动排除和修复错误。强烈的打击.如果进行了更改,请记录所需更改的描述,以便在重新生成代码时可以轻松地重新生成更改。

    痛饮文档查找有关故障排除的指导。(例如SWIG- e开关在预处理器运行后输出结果。)也许可以从删除所有SWIG功能开始,然后逐渐添加特性。在网上查找有关如何修复包的错误的信息。

f2c

f2c是一个将Fortran77代码转换为C/ c++代码的工具。使f2c代码跨平台所需的维护负担是巨大的。因为需要安装fortran编译器(或模拟器)R, f2c通常是不必要的。有几个包使用原生fortran代码没有问题。

解决方案:如果可能的话,取消f2c。唯一的方法是巧妙处理makefile,使每个受支持的平台或多或少都有一个目标makefile。请填写Bioc-devel邮件列表如果你有困难。