是否通过Objective-C中的clang优化了常量?

时间:2017-01-06 10:49:09

标签: objective-c const

我以前使用预处理器宏来定义常量,然后发现它们是个坏主意。它们位于名为Global.h的头文件中。所以我创建了一个Global.m文件,并使用以下内容从git中忽略它:

#if DEBUG

BOOL const GodMode = YES;
BOOL const TutorialDisabled = YES;

#elif STAGING

BOOL const GodMode = YES;
BOOL const TutorialDisabled = NO;

#else

BOOL const GodMode = NO;
BOOL const TutorialDisabled = NO;

#endif

Global.h有此内容:

extern BOOL const GodMode;
extern BOOL const TutorialDisabled;

STAGING宏在项目设置Preprocessor Macros中为我的自定义Configuration Staging STAGING=1定义为DEBUGDEBUG=1宏是Xcode生成的默认宏,类似于暂存:Debug

有时,我想编写仅针对#if DEBUG // do something #endif 配置执行的代码,而无需添加新常量。

所以我开始使用:

// do something

有这些优点/缺点:

  • 的优点:

    • !对于其他配置甚至不存在,因此应用规模不会增加且不存在拼写错误的风险(例如添加// do something ),这将导致它在不需要时运行
  • 缺点:

    • "不必要的编译文件"问题又回来了;每次我更改运行配置时,需要再次编译使用它的文件,这会增加构建时间
    • 奇怪的语法风格;我不知道是否应该增加#if的缩进以匹配if (Debug) { // do something } 或其周围的代码,或者我是否应该添加如上所述的空行,或者是否应该在与代码相同的缩进级别的行的开头;有类似的东西:
Global.h

听起来更好主意,所以我将extern BOOL const Debug; extern BOOL const Staging; extern BOOL const Release; extern BOOL const GodMode; extern BOOL const TutorialDisabled; 更改为:

Global.m

#if DEBUG BOOL const Debug = YES; BOOL const Staging = NO; BOOL const Release = NO; BOOL const GodMode = YES; BOOL const TutorialDisabled = YES; #elif STAGING BOOL const Debug = NO; BOOL const Staging = YES; BOOL const Release = NO; BOOL const GodMode = YES; BOOL const TutorialDisabled = NO; #else BOOL const Debug = NO; BOOL const Staging = NO; BOOL const Release = YES; BOOL const GodMode = NO; BOOL const TutorialDisabled = NO; #endif

static BOOL const Debug = DEBUG;
static BOOL const Staging = STAGING;
static BOOL const Release = (!DEBUG && !STAGING);

并且有效。

出于某种原因,

Use of undeclared identifier 'STAGING'

抛出错误DEBUG,即使它的定义与DEBUG=0完全相同:这意味着其他配置没有//:configuration = Debug GCC_PREPROCESSOR_DEFINITIONS = DEBUG=1 //:configuration = Staging GCC_PREPROCESSOR_DEFINITIONS = STAGING=1 //:configuration = Release

Preprocess

但是,查看if (Debug) {助理编辑器视图,if会显示出来。

我的问题是,public class App { StringTokenizer st = new StringTokenizer("Pete likes Mathematics 3+3 and Jessica too 6+3.", " \t\n\r:;.!?,/\\|\"\'", true); public static void main(String[] args) { new App(); } public App(){ ArrayList<String> renderedStrings = new ArrayList<String>(); while(st.hasMoreTokens()){ String s = st.nextToken(); if(!AdditionPatternFuntion.render(s, renderedStrings)){ renderedStrings.add(s); } } for(String s : renderedStrings){ System.out.print(s); } } } 检查是否会实际保留在发布配置的代码中?如果是,是否有更好的方法来实现我之前描述的内容?

1 个答案:

答案 0 :(得分:2)

没有比告诉自己更好的方式了。通过我的Globals.hGlobals.m文件,我写了以下main.m

#import "Globals.h"
#import <Foundation/Foundation.h>

int main()
{
    @autoreleasepool {
        if (Debug) {
            puts("We're in debug mode");
        } else if (Staging) {
            puts("We're in staging mode");
        } else {
            puts("We're in release mode");
        }
    }
}

非常简单,只需打印出我们所处的模式。让我们编译并运行它以查看会发生什么:

$ clang -g -fobjc-arc -o var_test main.m Globals.m && ./var_test
We're in release mode

看起来合理 - 我们没有提供DEBUGSTAGING,因此我们必须处于发布模式。 (-g告诉clang输出调试信息,如果它有用; -fobjc-arc打开ARC; -o告诉clang输出具有特定名称的可执行文件。)测试:

$ clang -g -DDEBUG -fobjc-arc -o var_test main.m Globals.m && ./var_test
We're in debug mode
$ clang -g -DSTAGING -fobjc-arc -o var_test main.m Globals.m && ./var_test
We're in staging mode

一切都很好,看起来很有效! (-DDEBUG在预处理器中定义DEBUG; -DSTAGINGSTAGING执行相同操作。)现在的问题是,clang是否删除了if语句并替换了代码只是一个打印声明?我不会假装你的装配技巧,所以让我们来看看编译器通过Hopper的伪代码模式生成的内容(这会在生成的目标代码中加载)由编译器反向反汇编成类似C代码的东西:

int _main() {
    var_10 = objc_autoreleasePoolPush();
    if (*(int8_t *)_Debug != 0x0) {
            puts("We're in debug mode");
    }
    else {
            if (*(int8_t *)_Staging != 0x0) {
                    puts("We're in staging mode");
            }
            else {
                    puts("We're in release mode");
            }
    }
    objc_autoreleasePoolPop(var_10);
    rax = 0x0;
    return rax;
}

变量名称消失了,这看起来有点奇怪的C /汇编混合,但很明显if语句仍在那里...

好吧,好吧,也许如果我们尝试优化构建一点点?也许Clang在这里没有做任何优化。

$ clang -g -O2 -fobjc-arc -o var_test main.m Globals.m

-O2启用了一些激进的编译器优化。)Hopper输出:

int _main() {
    rbx = objc_autoreleasePoolPush();
    if (*(int8_t *)_Debug != 0x0) {
            rdi = "We're in debug mode";
    }
    else {
            if (*(int8_t *)_Staging != 0x0) {
                    rdi = "We're in staging mode";
            }
            else {
                    rdi = "We're in release mode";
            }
    }
    puts(rdi);
    objc_autoreleasePoolPop(rbx);
    return 0x0;
}

好的,略有不同,但它还在那里!是什么给了什么?

对于许多编译语言,包含C / Objective-C,编译只需几个阶段即可完成。您在项目中包含的每个.m文件都被单独编译为目标代码(编译阶段),然后所有生成的目标代码文件一起链接(链接阶段)。这里的关键是每个.m文件都是单独编译的 - 即使变量是在Globals.m中静态定义的,当编译main.m时,编译器也不知道DebugStaging的值 - 它只看到Globals.h中的声明表示&#34;嘿,有线下的人会提供实际值这些变量,我保证&#34 ;;它由链接器将这些粘合在一起。 (这就是为什么你可以将main.m编译成main.o就好了,即使你忘记定义DebugStaging - 只有当你试图< em>将所有.o文件链接到一个可执行文件中,您收到链接器错误,告诉您存在问题。)

好的,这是一个链接问题,那么,不是编译问题。我们有什么方法可以让链接器为我们解决这个问题吗?是的,是的。 :)

$ clang -g -flto -fobjc-arc -o var_test main.m Globals.m && ./var_test
We're in release mode

料斗输出:

int _main() {
    rbx = objc_autoreleasePoolPush();
    puts("We're in release mode");
    objc_autoreleasePoolPop(rbx);
    return 0x0;
}
啊,好多了! -flto标志启用Link Time Optimization,允许链接器查看.o文件,因为它将它们粘合在一起并根据它看到的内容进行优化。在启用优化的情况下链接将花费更长的时间(您可以在目标的Xcode中的Build Settings中启用LTO),但会执行这样的优化。

现在,对于真正的问题:这甚至是一个很好的方法吗?呃,也许不是。

为此开启LTO有点矫枉过正。在您的代码中使用#if DEBUG没有任何害处,而且它实际上相对常见。你可以保持缩进完全相同:

int main()
{
    int x;
    if (/* some calculation setting x */) {
#if DEBUG
        NSLog("Calculation succeeded: %d", x);
#endif

        // ...
    }
}

或者,如果您更愿意坚持使用变量方法,欢迎您在static中将变量定义为Globals.h变量,完全取消Globals.m(和摆脱LTO的需要):

#if DEBUG
static BOOL const Debug = YES;
static BOOL const Staging = NO;
static BOOL const Release = NO;

static BOOL const GodMode = YES;
static BOOL const TutorialDisabled = YES;
#elif STAGING
static BOOL const Debug = NO;
static BOOL const Staging = YES;
static BOOL const Release = NO;

static BOOL const GodMode = YES;
static BOOL const TutorialDisabled = NO;
#else
static BOOL const Debug = NO;
static BOOL const Staging = NO;
static BOOL const Release = YES;

static BOOL const GodMode = NO;
static BOOL const TutorialDisabled = NO;
#endif

您也可以使用

static BOOL const Debug = DEBUG;
static BOOL const Staging = STAGING;
static BOOL const Release = (!DEBUG && !STAGING);

版本。它不工作的原因正是你自己说的:&#34;其他配置没有DEBUG = 0&#34;。如果您没有定义DEBUG,那么编译器应该对static BOOL const Debug = DEBUG;做些什么?您应该在暂存和释放模式中定义DEBUG=0,在调试和释放模式下定义STAGING=0以使其正常工作。

希望这有帮助!