GCC 4.0 移植建议

http://physics.ycool.com/post.1774373.html

 

 

GCC 4.0 移植建议

在 Mac OS X 10.4 系统上,GCC 4.0 是缺省的编译器。如果您在这个平台上创建新的工程,自然就会使用 GCC 4.0 来进行工程的编译。如果您正在用 GCC3.3(Mac OS X 10.3 的缺省编译器)连编现有的工程,这里有很多理由可以使您将它升级到 GCC 4.0。这些理由包括:

  • 更少的编译次数

  • 更好地遵循C++语言标准

  • 更小的C++二进制代码尺寸

  • 更快的C++编译速度

  • 更好的优化结构

  • 更好的错误检查和诊断

然而,在升级之前您应该了解GCC 4.0有什么变化,这些变化可能如何影响您的代码。特别值得一提的是,在GCC 3.3上可以很干净地进行编译的工程,在GCC 4.0上可能会产生警告和错误。这并不是要阻止您进行GCC 4.0的升级。升级可能会帮助您找到一些不易发现的错误,并且使您的代码更好地遵循现有的标准。

本文档将为如何把代码迁移到GCC 4.0提供信息和指导。如果您需要额外的信息,特别是关于C++支持的变更信息,请参见下面这些GCC发布说明:

请注意:苹果电脑的GCC 4.0发行版在自由软件基金会(Free Software Foundation,简称FSF)宣布正式版本之前就已经出现了。苹果的GCC 4.0发布说明中包含一个很好的总结,概括了苹果公司编译器版本的变化。

 

本部分包括如下主要内容:
迁移的优势
进行GCC 4.0的转换
步骤1:改用GCC 4.0连编工程
步骤2:在GCC 4.0上编译代码
步骤3:解决连接错误
步骤4:确认您的代码
步骤5:消除警告


迁移的优势

GCC 4.0代表着在GCC 3.3基础上的一个重要提升。对于大多数开发者来说,我们强烈推荐进行升级。不仅因为这个新的编译器具有更好的性能,而且因为它更加严格地遵循现有的标准,确保您的代码更加正确。

重要提示:GCC 4.0使用了Mac OS X v10.3.9系统引入的libgcc和libstdc++的动态库实现。如果您迁移到GCC 4.0,则新创建的二进制代码只能运行在Mac OS X 10.3.9及更新的系统上。如果需要更多地了解这些动态库可能给代码带来的影响,请参见C++运行环境编程指南

 

性能

GCC 4.0对代码的编译次数更少,特别是C++代码。C++程序的编译性能提高多达30%。C和Objective-C程序则提高5%。

代码优化

在代码优化方面,GCC 4.0现在使用一个称为静态单一任务(static single assignment)或者SSA的模型。SSA通过更好和更多的优化,使您的程序运行速度更快,效率更高。SSA优化器在寻找哪里可以进行函数嵌入方面工作得更好。随着开发者对SSA优化的不断扩展,在GCC未来的修订版中还可能得到更好的优化结果。

更好的警告

警告信息通常指出代码中潜在的错误。GCC 4.0执行很多深入的检查(不管优化功能处于激活还是禁用状态),因此也报告更多的警告信息。指出潜在的问题可以帮助您修正代码,提高整体的稳定性。举例来说,现在GCC 4.0会对可能含糊不清的隐式类型转换发出警告,而这种类型转换可能导致对未知或者意料之外的对象进行操作。

GCC 4.0还将一些警告升级为错误。举例来说,如果您现在试图使用来自某个Objective-C方法的返回值,而该方法的返回值类型为void,则GCC 4.0会报告一个错误,如下面的实例所示:

#import <Foundation/Foundation.h>
 
 
 
@interface Foo : NSObject
 
{
 
    int _i;
 
};
 
 
 
- (void) i;
 
@end
 
 
 
@implementation Foo
 
- (void) i 
 
{
 
    return _i;
 
}
 
@end
 
 
 
int main(int argc, char **argv) 
 
{
 
    Foo *f=[[Foo alloc] init];
 
    int value = [f i];  // this is a hard error in GCC 4.0.
 
}
 

还有一点是当您在C++代码中将一个数组赋值给另外一个数组,GCC 4.0也会报告为错误。如下所示:

#include <string.h>
 
 
 
main(int argc, char **argv) 
 
{
 
    int ARRAY_SIZE = 5;
 
    int a[5], b[5];
 
 
 
    b[0] = 1;
 
    a = b; // ERROR with GCC 4.0; Warning with GCC 3.3.
 
    memcpy(a,b,sizeof(int) * ARRAY_SIZE); // This is the correct way to copy an array in GCC 4.0.
 
}
 

在编译上面的代码时,GCC 会报告一个类似下面的错误:

/tmp/foo.C:9: error: ISO C++ forbids assignment of arrays
 

更好地遵循C++标准

GCC 4.0现在更加严格地遵循绝大部分主要编译器厂商支持的C和C++语言标准。更好地遵循语言标准意味着您的代码更加正确,也意味着可以更好地移植到其它平台。如果您的代码不符合标准,则编译器会发出警告告诉您。如果您开发的代码准备用于多个平台,则仅仅这个特性就应该值得让您升级了。

编译器的稳定性

GCC 4.0修复了大量的错误,如果您是使用GCC 3.3的C++程序开发者的话,可能会注意到GCC 4.0中修复了很多错误,这部分归功于提升了的C++支持和新的C++解析器。如果您曾经在解析复杂的C++代码的时候崩溃了,则肯定应该升级到GCC 4.0。

编译器的支持

现在,GCC 4.0是Mac OS X v10.4的缺省编译器。这意味着当前开发努力的着眼点是如何使GCC 4.0变成一个更好的编译器。即使您不需要编译器提供的新功能或者好的性能,升级也意味着您可以使用最新的,集成了最近的错误修复代码的编译器。

当然,值得说明的是苹果仍然支持GCC 3.3,并且将GCC 3.3编译器和GCC 4.0一起发行。然而,基于GCC 3.3的代码库将不能受益于某些关键的错误修复带来的好处。

进行GCC 4.0的转换

转换到GCC 4.0的过程相对直接,可以总结为下面这些步骤:

  1. 将工程进行转换,改用GCC 4.0进行连编。

  2. 编译工程,处理编译时的错误。

  3. 处理连接错误。

  4. 对代码进行正当性检查,确保其行为和以前一样。

  5. 积极消除各种警告信息。

下面的部分将提供上述每个步骤的详细信息。

重要提示:在下面的部分中,请特别注意警告信息和这里这种重要提示。这些信息指出一些容易引起误解的内容,和可能使您的注意力偏离问题的本质错误信息。

 

步骤1:改用GCC 4.0连编工程

使用GCC 4.0要做的第一件事就是配置您的工程。即使您的工程使用缺省的系统编译器(现在是GCC 4.0),可能也需要修改传递给编译器的选项。升级到GCC 4.0的基本步骤如下:

  • 修改Xcode的目标或者makefile。

  • 检查编译器和连接器的选项。

  • 检查预处理器的使用方法。

下面部分将提供上述每个步骤的详细信息。

修改Xcode目标

如果您用Xcode对工程进行连编,转换到GCC 4.0的工程就包括修改工程中每个目标的设置。如何修改这些设置取决于目标的类型。

目标类型

修改

本地目标

代表本地目标的图标看起来象一个盒子。对于本地目标,您可以打开该目标的Inspector窗口,选择Build Rules(连编规则)面板,如果System C Rule(系统C规则)显示的不是期望的编译器,则点击+按键增加一个新的连编规则。将新的连编规则配置为使用GCC 4.0处理“C源代码文件”(或者将编译器配置为“GCC系统版本(4.0)”)。

经典目标(基于Jam连编系统)

代表经典目标的图标看起来象一套同心圆(公牛眼)。对于经典目标,双击工程窗口中的目标,将它在编辑器中打开,选择Settings(设置) > Simple View(简单视图) > GCC Compiler Settings(GCC编译器设置),从弹出菜单中的编译器版本中选择GCC 4.0编译器。

!

警告信息:一些开发者试图在Xcode的目标专家视图中对CC和CPLUSPLUS设置进行赋值,从而硬性指定Xcode中的编译器。在这种情况下,编译器可能会抱怨某些正在使用的选项,比如headermaps。问题在于虽然Xcode认为它正在使用GCC 4.0,但是实际上向编译器传递的仍然是GCC 3.3的选项。为了解决这个问题,需要删除CC和CPLUSPLUS设置的值。对于经典目标(基于Jam连编系统),可以在目标设置窗口中寻找指向编译器版本的设置;对于本地目标,则可以在Build面板中查看这些设置。

在某些情况下,您可能希望看到Xcode用哪个编译器编译您的代码,并确认传递给编译器的选项。为了在Xcode中得到这些信息,可以打开Build Results(连编结果)窗口并显示“build log(连编日志)”面板(这个部分位于错误列表和源代码面板之间,并不总是可见的)。您也可以使用这个面板来查看连接器错误和警告信息。如果需要更多如何使用连编日志的信息,请参见Xcode 2.0 用户指南

修改Makefile

如果您的工程使用Makefile或定制的脚本进行连编,则需要找到引用/usr/bin/gcc-3.3的地方,并将它们修改为/usr/bin/gcc或者/usr/bin/gcc-4.0。缺省情况下,/usr/bin/gcc启动的是缺省的系统编译器,在Mac OS X v10.4上就是GCC 4.0。

改变编译器和连接器选项

某些工程可能使用了GCC 3.3可以支持,而GCC 4.0中已经被删除的选项。您可以通过显式地检查所有Xcode工程的编译器选项来定位这些变化,或者也可以简单地对工程进行连编,然后等待与非法选项有关的错误信息。一旦您识别出错误的选项,就可以到相应目标的设置界面上,将该选项删除。表2列举出其它一些应该考虑修改的编译器选项。

选项

描述

-Werror

这个选项在GCC 4.0上仍然支持,但是由于它将警告转化为硬性的错误,您可能希望干脆关掉它。GCC 4.0已经产生比以前的版本多得多的警告,因此在您试图使代码通过GCC 4.0的连编并运行起来的时候,这个选项就不是那么必要了。如果要关闭这个选项,在目标的连编设置面板上关闭"Treat warnings as errors(将警告处理为错误)"选项就可以了。

-Wno-precomp

这个选项自GCC 3.1之后就不需要了。

-fcoalesce, -fweak-coalesced, -fcoalesce-templates

没有必要使用这些选项了,因为这些选项指定的功能现在是缺省的了。这些选项可以安全地从工程中删除了。

-fno-coalesce, -fno-weak-coalesced, -fno-coalesce-templates

这些选项可以安全删除了。在早期编译器版本中使用这些选项的工程通常是为了绕过编译器的错误,而这些错误已经不存在了。在某些非常少见的场合下,可能真的需要关闭C++的"vague linkage(模糊连接)"功能,这时可以使用-fno-weak选项。

--param max-inline-insns, --param min-inline-insns

这些选项已经不再使用了。嵌入(inlining)参数使用的值在GCC 3.3和GCC 4.0这两个版本上的意义不同。这些用于GCC 3.3上的参数在GCC 4.0上会使导致编译性能变慢,以及使二进制代码的尺寸变大。您可能希望删除所有的嵌入选项,分析代码中哪些部分的代码速度慢,然后再设置嵌入参数,使慢的函数可以被嵌入。


检查预处理器的使用方法

快速检查不合适的预处理器使用方法。如果您的工程中包含根据编译器(比如GCC和CodeWarrior)进行条件分支的代码,则别忘了更新通过__GNUC__变量识别编译器的条件代码,如下面的代码列表所示:

if (__GNUC__ == 3) // old
 
 
 
if (__GNUC__ == 3) || (__GNUC__ == 4) // new
 

在工程中检索__GNUC__可以定位所有的相关代码。

步骤2:在GCC 4.0上编译代码

一旦您开始编译代码,则最常见的问题根源有如下几点:

  • 不正当的编译器和连接器选项

  • C++模板代码的变化

  • 对赋值语句(lvalues)左边部分的内容所做的限制

  • 寻找头文件时出现的问题

  • 互相冲突的声明(static和extern)

  • 内核扩展("kext")源代码的变化

  • 错误代码的警告

如果需要有关不正当的编译器和连接器选项的更多信息,请参见“改变编译器和连接器选项”部分。其余的问题在本文接下来的部分中进行解释。每个部分除了解释问题的类型之外,还将讨论在运行连编完成的二进制代码时可能碰到的问题。

C++模板的变化

C++程序员最常碰到的问题和编译器为遵循C++标准而进行的改变有关系。GCC 3.4(发行的组织是FSF,不是苹果公司)开始对错误的C++使用模式进行警告。在现在的GCC 4.0中,那些警告变成了错误。

您可能碰到的一个比较常见的错误是名字查找问题,在形式上表现为“在当前的作用域上没有定义”。在GCC 4.0的文档中检索“name lookup(名字查找)”可以找到更多关于这个问题的信息。最常见的四个名字查找错误如下:

错误

描述

模板类中有依赖关系的名字

在函数模板中的名称可以是有依赖关系的,也可以是没有依赖关系的。有依赖关系的名称在词汇上依赖于模板参数,在模板实例化的时候进行名称查找;而没有依赖关系的名称在模板被解析的时候进行名称查找,该过程发生在模板被实例化之前。

模板中的未限定名称

在模板参数类中的名称必须进行限定,可以显式地说明该名称来自模板(Foo<T>::mArray),也可以使用this指针来指明该名称来自模板的实例。如果您显式地声明某名称来自模板,相应的名称查找会在实例化时进行;否则,如果您使用this指针,则名称查找在模板定义时进行。

模板超类中的未限定名称

在当前模板的模板超类中定义的名称必须限定为来自超类。另外一种方法是在那些名称之前加上this->来进行限定。

被继承的模板类中的未限定名称

如果一个模板类继承自另外一个模板类,则被继承的模板类的成员必须进行限定。请参见列表1中的实例。

列表1中显示一个属于被继承的模板类的未限定名称的例子。

template <typename T> struct Base 
 
{
 
    void f();
 
};
 
 
 
template <typename T> struct Derived : public Base<T> 
 
{
 
    void g() 
 
    {
 
        f();          // ERROR: Name not found.
 
        this->f();    // OK
 
        Derived::f(); // OK
 
        Base<T>::f(); // OK
 
    }
 
};
 

lvalue赋值的变化

GCC编译器在此前进行了扩展,支持在赋值操作的左边部分(赋值操作的lvalue)使用non-trivial的表达式。为了更好地遵循C和C++的语言标准,也因为程序员的意图在那些情况下并不总是很明显,GCC 4.0将这个特性删去了。

目前,对于一些在FSF版本的GCC 4.0编译器中处理为错误的情况,苹果电脑版本的GCC 4.0编译器仅仅处理为警告。在苹果电脑将来版本的编译器上,对于那些使用场景的支持会被删去。下面是一些现在可能产生错误或者警告的代码实例。

在下面这些情况下,GCC 4.0不再允许lvalue赋值。

(condition ?  lvalue1 : lvalue2) = expr; // Warning: target of assignment not really an lvalue (will be a hard error in future)
 

为了避免这种问题,需要重写表达式,使赋值表达式的左边只包含一个变量。

可能产生错误的另一种情况是对一个lvalue变量进行取址,如下面所示:

&foo = 1; //  ERROR: invalid lvalue in assignment
 

可以为foo的地址创建一个临时的变量来代替这个表达式,并对它进行赋值。

对于最后那种情况,问题的根源在于这样的用法首先取得引用,然后再转换为目标类型。相反,应该将指针转换为正确的指针类型,然后在进行取值。

(u_int32_t)*(vcp->vc_outtok) = sp->sv_caps; // Warning: target of assignment not really an lvalue (hard error in future)
 
 
 
*(u_int32_t*)(vcp->vc_outtok) = sp->sv_caps; // OK
 

检索框架的头文件

框架和头文件检索中的几个问题在GCC 3.3和GCC 4.0上有所不同。

在GCC 3.3中,编译器在检索框架中的某个头文件的时候会错误地检索所有的框架路径。这种行为在某些情况下可能导致从不同安装版本的框架中取得头文件,所以是不正确的。举例来说,它可能会导致某些头文件从您正在使用的框架的拷贝中取得,而另一些头文件则从框架的正式安装版本中取得。

GCC 4.0现在使用一个称为“首次匹配获胜”的模型来处理框架。在这个模型下,当您首次包含一个框架头文件时,编译器会注意到该框架的位置。之后,如果要从同样的框架中取得头文件,编译器就自动访问同样的位置。

重要提示:如果编译器报告找不到某个头文件,而您知道该头文件存在于框架中,则可能就是因为这里讨论的框架头文件匹配错误的问题导致的。为了解决这个问题,可以重新对框架路径进行排序,使框架的完全版本首先被检索。

 

在GCC 3.3中,-I-F选项被处理为可互换。这是一个不正确的行为。在GCC 4.0中,您必须使用-F选项来为框架目录获取正确的检索语义。

重要提示:如果编译器报告找不到框架的头文件,您也应该进行检查,确保用-F选项包含了框架。

 

gcc -I/System/Library/MyFrameworks foo.m // worked in gcc-3.3, doesn't work in gcc-4.0.
 
gcc -F/System/Library/MyFrameworks foo.m // OK
 

互相冲突的声明

下面部分列举了一些可能碰到的互相冲突的声明:

Static和Extern

现在,如果您在工程中用externstatic关键字声明同一个变量,GCC 4.0会向您发出警告。过去,编译器则是以最后出现的声明为准。

当您在一个文件中创建一个局部的静态(static)变量,然后在另一个文件中将同名变量声明为extern,就会出现这种问题。请不要使用这样的做法,相反,应该修改静态变量的名称,使之不同于全局变量的名称。


// foo.h:
 
extern int _defaultWidth;  // used by lots of compilation units
 
 
 
// foo.c:
 
static int _defaultWidth; // local copy -- ERROR: static declaration of '_defaultWidth' follows non-static declaration
 

静态函数的预先声明

GCC 4.0不支持将一个静态函数的预先(forward)定义直接放到另一个函数的函数体内部。为了修正这个问题,可以将声明移到函数的外部(移到文件名字空间的级别上)。


int foo(int i) {
 
  static int bar(int j);  // ERROR: invalid storage class for function 'bar
 
  bar(i+5);
 
}
 
 
 
static int bar(int j);  // OK
 
int foo(int i) {
 
  bar(i+5);
 
}
 

C++ new

GCC 4.0增强了为对象数组分配空间的规则,new语句中的类名称不能用括号包围。


map = new (IOMemoryMap*)[WindowCount]; // ERROR: array bound forbidden after parenthesized type-id
 
// note: try removing the parentheses around the type-id
 
 
 
map = new IOMemoryMap *[WindowCount];  // OK
 

C++成员函数的隐含参数

在成员函数的声明和定义中不能同时指定同样的隐含参数。在类外部声明的成员函数定义中可以给定其它的缺省参数,但是不能和类内部声明的参数一样,如下面的例子所示:


class A 
 
{
 
    int i;
 
    int foo(int i=99, int j);
 
};
 
 
 
int A::foo(int i=99, int j) // ERROR: default argument given for parameter 1 of 'int A::foo(int)'
 
    // ERROR: after previous specification in 'int A::foo(int)'
 
{  
 
 ...
 
}
 
 
 
int A::foo(int i=99, int j=10) {  // OK, but visible only in this compilation unit
 
 ...
 
}
 

内核扩展

GCC 4.0可以编译内核扩展程序(kernel extensions,即kexts),且编译出来的内核扩展应该可以和现在及以前的Mac OS X版本(必须遵守由GCC 3.3编译而成的kext所遵守的所有条件)协同工作。尽管如此,您还是需要对kext进行一些变化,才能使其编译通过。

内核扩展中不能使用需要调用C++构造函数和静态初始化的auto(函数级别)变量。这是因为内核不能为auto变量的初始化提供锁定例程(在内核中,这样的例程会引起危险的副作用)。您的代码应该可以通过编译,但是不能装载,因为加锁代码的符号(___cxa_guard_abort___cxa_guard_acquire,和___cxa_guard_release)没有定义。要解决这个问题,必须将局部变量移到顶层:

static A* globalArrayOfA = new A[10];
 
 
 
int A::foo(int i) 
 
{
 
    static A* arrayOfA = new A[10];
 
    return arrayOfA[i].value();  // KEXT LOAD ERROR: __cxa_guard_abort undefined
 
    return globalArrayOfA[i].value();  // OK
 
}
 

很多kext试图对成员函数的指针进行类型转换,使其看起来好像简单的函数指针。这样做使成员函数可以被注册为中断处理函数。GCC 4.0不允许执行这样的类型转换。相反,您必须使用OSMemberCastFunction宏才能正确地进行转换。这个宏可以在Mac OS X v10.4上使用,在GCC 3.3或者GCC 4.0上可以同样工作,用GCC 3.3编译时,其行为和原有的代码是一样的。

int A::bar(int i) 
 
{
 
    handler = (IOInterruptAction) &A::interruptHandler; // ERROR: converting from `int (A::*)(int)' to `int (*)(int)' in a kext.  Use OSMemberFunctionCast() instead.
 
 
 
    return (*handler)(5);
 
} 
 
 
 
int B::bar(int i) 
 
{
 
    handler = OSMemberFunctionCast(IOInterruptAction, this, &B::interruptHandler); // OK
 
    return (*handler)(5);
 
}
 

对不正确代码的警告

下面的部分对一些编译过程中可能遇到的其它类型的错误和警告进行解释。

声明抽象虚函数

请不要使用NULL来定义未实现的虚函数。相反,应该用数值0来定义,如下面的实例所示:


class C 
 
{
 
    virtual int PrepareCFBundle() = NULL; // ERROR: invalid initializer for virtual method
 
 
 
    virtual int PrepareCFBundle() = 0; // OK
 
};
 

标签

ISO C规范说明标签之后应该跟随一个语句。因此,在代码块的结束部分不能有标签:


foo: }
 

要修复这个问题,只需简单地在标签后面增加一个分号,创建一个空语句就可以了:


foo: ; }
 

嵌入函数中的静态数据

如果您在嵌入函数内部定义一个静态数组,则GCC 4.0会报告一个错误。编译器无法确定应该使用单一的数组,还是在嵌入时对数组进行复制。为了修复这个问题,可以将数组移出函数。

offsetof函数

GCC 4.0要求offsetof函数的参数必须是常量:


bufferSizeNeeded = offsetof(ATSUGlyphInfoArray, glyphs[numGlyphs]); // ERROR:
 
 
 
// Instead, do this.
 
bufferSizeNeeded = (char*)&(((ATSUGlyphInfoArray*)0)->glyphs[numGlyphs]) - (char*)0;
 

Bitfield和typeof操作符

由于C编译器的变化,试图获取一个bitfield值的类型在GCC 4.0上会产生错误。GCC 3.3允许在C代码中进行这样的操作,而且GCC 4.0也支持在C++代码中进行这种操作。


// The following macro won't work in GCC 4.0 because the type of
 
// value is considered to be a bitfield that can't be used in typeof.
 
// In GCC 3.3, it would have been treated as an int.
 
 
 
#define STRUCT_VALUE(x) ((x->isSet ? x->value : (typeof(x->value))0))
 
 
 
struct a
 
{
 
    int isSet: 1;
 
    int value:15;
 
};
 
 
 
int foo(struct a *instance) 
 
{
 
    return STRUCT_VALUE(instance);
 
}
 

在某些情况下,您可以在C代码中修正这个问题。特别是当bitfield值是一个2-bit整数时,可以将typeof语句中使用的值强制类型转换为int。这应该可以是代码通过编译。

初始化类实例变量

在GCC 4.0和C++中,只有当类型是整型数或者枚举,而且被分配的值为常数的时候,静态的类实例变量才能在类的声明内部中进行初始化。如果您需要使用表达式来进行初始化,则不能在类的声明中进行。相反,应该在类的定义文件中进行赋值。

数组赋值

您不能将一个数组赋值为另外一个数组,然后期望编译器为您拷贝数组的成员。在需要拷贝数组的时候,您必须用bcopy来显式地进行数组字节的拷贝。GCC 3.3对这种类型的行为会发出警告,而在GCC 4.0上,这种行为会导致硬性的错误。

定义可以改变头文件处理的系统宏

某些开放源码的程序不能进行连编,因为编译器认为缺乏某些文件。这个问题的根源是工程的源代码文件或者编译器参数中定义了实现保留(implementation-reserved)的名称,比如__FreeBSD__。实现保留名称以两个下划线字符或者一个下划线跟着一个大写字母开头。这些名称通常控制编译器使用哪些文件。如果定义了__FreeBSD__宏,而没有设置值,可能会使来自BSD的头文件认为您当前运行在早期的BSD版本比上,而实际上并不是。

重要提示:您应该慎重地查看工程中定义的实现保留宏,因为它们可能改变系统解析头文件的方式。定义__FreeBSD__宏引起的一个结果是编译器报告不能找到machine/ansi.h头文件。

 

如果定义了__FreeBSD__,则头文件会认为您现在运行的BSD的版本低于版本5。版本5之前的BSD中包含machine/ansi.h头文件。Mac OS X源自版本5的BSD,该版本中没有包含头文件。因此,编译器在不能找到该文件时会报告错误。

一般性的纠错

GCC 4.0在很多情况下都会产生错误,而同样的情况在其先前版本的编译器(GCC 3.3)上没有问题。您一旦对代码进行检查就会发现,大多数这样的错误是很明显的。这些错误包括下面的场景:

  • 访问C++和Objective C的私有实例变量。

  • 在期望有返回值的函数中没有返回值。

  • 在返回值类型为void的函数中返回一个值。

  • 对返回值类型为void的函数的返回值进行操作。

  • 强制类型转换。编译器不允许将一个void指针隐式转换为其它类型的指针。相反,您必须提供显式的转换。

线程安全和静态局部变量

为了尽可能地线程安全,GCC 4.0自动在访问C++局部静态变量的代码上加锁。这些锁的目的是确保访问局部静态变量时的线程安全,但是如果您频繁地使用这样的变量,则可能会发现代码的性能倒退了。在这种情况下,请向编译器传入-fno-threadsafe-statics选项,以关闭加锁行为。如果在关闭之后性能正常,就可以确定问题的根源了。

如果您继续使用-fno-threadsafe-statics选项,请记住,您需要自行负责代码中局部静态变量的线程安全。

步骤3:解决连接错误

一旦您梳理并解决了所有主要的编译错误,就可以开始考察连接问题了。C++的开发者可能会发现丢失或者多次定义某些符号。

考察系统库的用法

解决连接错误的第一步是考察系统库的使用方法。在Mac OS X v10.3.9以及之后的操作系统中,libgcc(C的支持库)和libstdc++(标准C++库)都实现为动态库。这些库会被GCC 4.0编译器(而不是ld)自动地连接到您的程序中。如果您碰到多次定义这些库的符号问题,您可能在连接的命令行中包含了这些库的静态版本。

请检查您的工程是否连接了libgcc.alibstdc++.a,或者libcc_dynamic.a静态库。如果您进行了连接,则应该将这些库从连接内命令中删除。

如果您在Xcode外面编译C++应用程序,请用g++(而不用ld)来进行连接,这样可以确保包含正确的库。

检查所有被连接的C++库

一旦您解决了所有与系统库有关的连接错误,就可以查看连接命令上的其它库了。如果您连接了包含C++接口,而且由GCC 3.3创建的库,则必须将其从连接命令行中删除。

C++应用程序二进制接口(ABI)在GCC 3.3和GCC 4.0之间发生了变化。与输出C++函数的库相连接的所有应用程序或者库都必须用同样的编译器进行连编。在您检查未定义符号列表时,这样的问题通常是很明显的。如果连接器报告任何以_ZTI开头的符号没有定义,则可能是因为您用混用了GCC 3.3和GCC 4.0的二进制代码。

请注意:_ZTI前缀指示的是特定C++类的损坏了的类型信息。

 

检查C++库的输出

如果您连编的是一个C++库,且限制从库中输出符号,则应该确认一下您的库是否以期望的方式进行符号输出。

缺省情况下,GCC 4.0将大多数的符号标志为私有,以防止它们被输出。这是对GCC 3.3行为的一个改变,但是有显著的好处,特别是对C++开发者。将符号可见性缺省设置为私有可以减少输出符号表的尺寸。如果您的代码广泛地使用模板,则会这个尺寸会非常显著地减少。

重要提示:如果您的工程是一个动态库,且使用输出列表,则可能要修改该列表,以确保从库中输出同样的符号。可以用nm工具来检查从GCC 3.3和GCC 4.0的二进制代码输出的符号列表。如果您输出C++接口,则请记住要同时输出输出类的类信息(_ZTI*符号)。

 

如果您不希望将大多数的符号设置为私有来换取空间效率的提升,可以告诉GCC 4.0以GCC 3.3的方式输出大部分的符号。在Xcode中关闭“symbols private by default(符号缺省私有)”设置就可以达到这个目的,在GCC命令行选项中则可以指定-fvisibility=default选项。

重要提示:在GCC 4.0和Xcode中,工程在缺省连编的时候会将所有的符号都标志为私有外部(private extern)。如果要输出符号,您需要关闭用于隐藏符号的全局设置,或者只标识那些希望输出的符号。否则,如果您使用nmedit来剥离不希望输出的符号,该工具会剥离所有的私有外部符号,而您可能不希望这么做。

 

更多关于C++符号可见性和运行时行为的信息,请参见C++ 运行环境编程指南

步骤4:确认您的代码

在成功连编应用程序之后,您应该开始测试,以确保程序以期望的方式进行工作。测试时应该使您的代码在所有的测试套件下运行,以确认编译器没有改变应用程序的行为。改变编译器可能会引入新的问题,因此测试是确认编译器变化是否平滑的重要步骤。

除了运行您的测试套件,您也应该检查连编完成的二进制代码的尺寸。用GCC 4.0进行编译时,编译器可以在之前不能进行优化的地方进行优化。因此您可能会发现,在采用同样的编译器设置的情况下,用GCC 4.0连编完成的二进制代码和之前用GCC 3.3连编的结果有很大的不同。可能有新的函数被嵌入,因此显著地增加了二进制代码的尺寸(更多这个效果的信息请参见“优化的变化”部分)。

如果您正在连编一个动态共享库,请确认库中输出的符号列表没有被改变,有关如何修复输出问题的更多信息,请参见“检查C++库的输出”

已知的代码变化

GCC 4.0的代码优化器和之前版本的GCC有很大的不同。如果您在重新编译的代码中发现不正确的行为,或者遭遇程序崩溃,请检查下面这些已知问题:

  • GCC 4.0的优化器改变了某些数学操作的计算顺序(比如加法操作或乘法操作)。

  • 将成员函数的指针转换为常规函数指针在GCC 4.0上的工作方式有所不同。在GCC 4.0之前,这样的类型转换在运行时进行识别,这意味着函数的地址是基于调用现场的对象的实际类型。举例来说,假定您在A的某个方法中请求A::MyFunction的地址,如果对象在运行时的实际类型是B(一个A的子类),且B定义了它自己的MyFunction版本,则您实际上得到的是B::MyFunction函数的地址。在GCC 4.0中,这些函数的地址是在编译时计算得到的。因此,如果您请求&A::MyFunction,将总是得到A::MyFunction的地址。

对于算术操作,C和C++标准的陈述是子表达式的求值顺序是没有定义的;编译器可以自由地以自己希望的顺序对加法或者乘法表达式中的参数进行求值。然而这个规则有些例外,包括下面这些类型的子表达式,都是从左到右进行求值:

  • 函数调用

  • &&操作符

  • ||操作符

  • ?: 操作符

  • ,(逗点) 操作符

如果这些子表达式之一受到影响,您就可能发现代码在GCC 4.0上行为有所不同,取决于优化器如何对调整代码顺序。

优化变化

GCC 4.0的嵌入算法在确定什么时候对函数进行嵌入,而不是进行调用的方面比先前版本的编译器好得多。然而,这个优化也有一些副作用,就是二进制代码的尺寸变得更大,代码速度变得更快。在很多情况下,如果您没有重载工程中控制嵌入的缺省参数,就不应该有显著的差异。如果您现在使用了-finline-limit=xxxx或者--param这些嵌入参数,则不要继续使用当前赋给那些参数的值。相反,应该重新分析代码的行为,看看那么参数可以在应用程序的代码尺寸和速度上提供最好的平衡。

内部的测试表明,在GCC 3.3下使用-finline-limit选项的工程在GCC 4.0下代码尺寸增加高达30%,编译时间增加高达50%。-finline-limit的缺省值通常是600。如果您用-Os选项来进行尺寸的优化,则缺省值下降到10。如果您为-finline-limit选项手工分配一个200或者更大的值,则连编完成的二进制代码的尺寸肯定比在GCC 3.3下使用同样的设置产生的代码大。

步骤5:消除警告

GCC 4.0对潜在的不正确代码提出警告的能力比先前版本的编译器好得多。因此,努力追究和消除代码中的警告信息是很重要的。新报告的警告信息已经揭示了Mac OS X系统源代码中的几个潜在错误,我们强烈建议您同样地消除自己的源代码中的警告。

一旦您的代码在GCC 4.0下成功连编,则可以开始考察编译器产生的警告信息了。一些警告可能反映出风格上的差异,或者指出编译器不能从代码中找出足够的信息来理解代码的原始意图。您应该考察每个警告信息,确定其到底是无害的,还是揭示了潜在的错误。排除潜在的问题不仅可以消除编译器的警告,也可以增加代码的稳定性。

下面的部分描述了一些较为常见的警告,您可能会在采用GCC 4.0进行编译的工程中看到。

Objective-C和选择器

当一个消息被发送给类型为id的接收者时,编译器会查找所有匹配的选择器,并对参数类型不一致的情况发出警告。GCC 4.0在这一点上比GCC 3.3更好,揭示出之前隐藏的不一致性。虽然这些警告现在是禁用的,您可以用-Wstrict-selector-match选项来打开。在将来的版本上,编译器会缺省地对这些不一致性发出警告。

下面是3个这种警告的实例,出现在Sketch实例程序中:


NSNotification.h:40: warning: using `-(void)postNotificationName:(NSString 
*)aName object:(id)anObject' NSDistributedNotificationCenter.h:59: 
warning: also 
found `-(void)postNotificationName:(NSString *)aName object:(NSString 
*)anObject'
 
 
 

NSNotification.h:37: warning: using `-(void)addObserver:(id)observer 
selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject' 
NSDistributedNotificationCenter.h:57: warning: also found 
`-(void)addObserver:(id)observer selector:(SEL)aSelector name:(NSString 
*)aName 
object:(NSString *)anObject'
 
 
 
NSView.h:115: warning: using `-(NSWindow *)window' 
 
NSAlert.h:125: warning: also found `-(id)window'
 

要修复这些警告,可以使用显式的类型转换,或者改变方法的定义,使调用和定义互相匹配。

Objective-C的实例变量

在Objective C中,实例变量的访问级别在缺省情况下被处理为@protected(保护)。现在,GCC 4.0显式地对因此产生的访问级别的问题进行警告,将来版本的编译器会将它处理为错误。为了纠正这个问题,您应该在Objective-C接口中显式地标识实例变量的作用域。

您不应该做的事

在一些结合使用某些特别难看或者奇怪的类型转换时,GCC4.0可能产生如下的警告信息:

Pictures.c: In function 'StdOpcode':
 
Pictures.c:1132: warning: function called through a non-compatible type
 
Pictures.c:1132: note: if this code is reached, the program will abort
 

在这些情况下,编译器实际上插入可一条abort指令,而不是产生它认为不正确的函数调用。如果您看到这种警告信息,应该删除或者修改相应的类型转换,使编译器可以调用正确的函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值