C++ gcc会优化掉对同一变量的重复函数调用,并且每次调用都有相同的输出吗?

C++ gcc会优化掉对同一变量的重复函数调用,并且每次调用都有相同的输出吗?,c++,gcc,optimization,compiler-construction,C++,Gcc,Optimization,Compiler Construction,对于一个应用程序,相同的信息以多种形式存在:Base64字符串、十六进制字符串和char[] 现在,为了提高效率,我不再费力地为每个函数声明和初始化一个变量,而是只在上述表单之间的明显转换点应用它。原因是在某些情况下,变量不需要转换为另一种形式进行条件比较等操作 从我所读到的,似乎编译器是难以置信的高效,而且一天比一天更高效;然而,当我试图阅读更深入的分析和描述时,我经常超越我的经验极限,我的大脑堆积如山 如果一个函数反复调用单个变量以将其转换为另一种形式,例如从Base64字符串转换为十六进制

对于一个应用程序,相同的信息以多种形式存在:Base64字符串、十六进制字符串和
char[]

现在,为了提高效率,我不再费力地为每个函数声明和初始化一个变量,而是只在上述表单之间的明显转换点应用它。原因是在某些情况下,变量不需要转换为另一种形式进行条件比较等操作

从我所读到的,似乎编译器是难以置信的高效,而且一天比一天更高效;然而,当我试图阅读更深入的分析和描述时,我经常超越我的经验极限,我的大脑堆积如山

如果一个函数反复调用单个变量以将其转换为另一种形式,例如从Base64字符串转换为十六进制字符串,每次都会产生相同的结果,那么编译器是否会优化这些调用,从而不需要为整个作用域声明一个变量


在我的例子中,我一直在使用
-Ofast
,直到有更好的东西出现。

编译器可以优化的地方实际上取决于代码的编写方式;然而,依赖编译器过于智能通常是不明智的。编译器非常擅长优化寄存器分配和各种低级内容,但是如果您知道程序中有一些不变量可以使代码编写更高效,那么不要假设编译器理解整个程序

对于您提到的这个特定示例,如果将数据包装在一个类中,该类实现了各种格式的转换运算符,并缓存转换结果,这将是一种比依赖编译器不重做相同计算要好得多的方法。但是,如果将这些转换运算符标记为“const”,则编译器可能会重用以前调用“const”方法的结果(假设未执行交叉非const操作)。但是,除了缓存结果之外,我建议您这样做,而不是依赖于此优化


此外,当涉及到这些优化时,唯一确定的方法是使用特定的编译器实际编译代码,并检查程序集输出,以确定它是否应用了该优化。

以下是一些代码来说明这一概念:

class TriString
{
  public:
    enum Format { Binary, Hex, Base64 };

    TriString(const std::string& s) : s_(s) { }

    // mutators - must modify b_ and h_ accordingly or clear them

    TriString& operator=(const std::string& rhs)
        { s_ = rhs; b_.clear(); h_.clear(); }

    TriString& erase(size_type index = 0, size_type count = npos)
    {
        s_.erase(index, npos);
        h_.clear(); // will need regeneration...
        b_.erase(index * 2, count == npos ? npos : count * 2);
    }

    char& operator[](size_type n)
    {
        h_.clear();
        b_.clear();
        return s_[n];
    }

    // ...add more as needed...

    // accessors

    const std::string& get(Format) const
    {
        if (Format == Binary || s_.empty())
            return s_;
        if (Format == Hex)
        {
            if (h_.empty()) h_ = to_hex(s_);
            return h_;
        }
        // Format == Base64
        if (b_.empty()) b_ = to_base64(s_);
        return b_;
    }

    const char& operator[](size_type n) const { return s_[n]; }

    // ...add more as needed...

  private:
    std::string s_;          // normal string

    // "cached" conversions - invariant: valid if not empty(), or s_.empty() too
    // (mutable so get(Format) const can modify despite being const)
    mutable std::string b_;  // base64 encoded
    mutable std::string h_;  // hex encoded
};
使用通常的
std::string
接口执行此操作并不安全,因为下面这样的客户端代码无法工作:

TriState s("hello!");
char& c = s[2];
const std::string& h = s.get(TriState::Hex);  // triggers caching of hex conversion
c = 'x';                                      // oops - modifies s_ without clearing/updating h_
const std::string& h2 = s.get(TriState::Hex); // oops - gets old cached h_ despite changed s_

您必须做出一些选择,要么限制接口以避免授予更改字符串的持续能力(如非
常量运算符[]
、迭代器等),要么返回代理对象(而不是字符引用),以便在写入时清除缓存的转换,或者记录一些对客户端使用的限制,并希望做到最好……

我希望
gcc
不会执行这种优化。如果是这样,则必须满足多个要求,比如同时编译被调用的函数,以提供在整个调用中优化寄存器的可能性

这样的优化令人印象深刻,但并非完全有用。程序员可以轻松地编写对函数的调用并保存返回值

CDC Cyber(c.1975)FORTRAN编译器具有有趣的行为。这将优化对的调用。这让很多写游戏的学生感到惊讶和困惑,比如掷两个骰子的片段:

integer roll
roll = mod (irand(0), 6)  +  mod (irand(0), 6)  +  2
这只会产生偶数,因为它认为它就像是写的一样

roll = 2 * mod (irand(0), 6)  +  2
它在1978年左右被报告为一个bug,通过不优化涉及
irand()
rand()
的表达式得到修复。通过使其更难优化,可以轻松解决:

integer roll, die1, die2
die1 = mod (irand(0), 6)  +  1
die2 = mod (irand(0), 6)  +  1
roll = die1 + die2

只要优化没有被调得太高,它就会像预期的那样工作。第一个例子总是经过优化:无法关闭。

lol,duh!你的意思是像一个
联盟
,在我的例子中,这三个表格是作为一个整体持有的?请理解,我在C++代码中的最基本的层次,你可以想象,在这一点上,当我不得不做任何事情,甚至可以被认为是危险的。非常感谢您的快速而翔实的回答!你介意给我举个简单的例子吗?提前非常感谢您!我实际上并不是指一个联合(联合一次只能保存它的一个值),而是指一个具有不同格式的“可变”指针的类,在初始化时,只有一个指针是非空的,而其他指针是在转换时计算和缓存的。(而且,在作业中,其他变量无效)。嗯,考虑到我的经验和智商不足,当你使用“指针”这样的危险词时,这绝对是一个我还不应该玩的地方。我想我大体上理解了你的意思:拥有一个通用的
类,它在初始化时将创建所有三种类型并相应地引用它们。在一个没有/没有那么高智商的水平上,这是正确的吗?再次感谢您分享您的优秀知识!这基本上是正确的,只是它会延迟初始化指针(即在转换时),在初始化时只初始化其中一个指针。哇,这太极端了!非常感谢你!你刚刚给了我一个模板来扩大我的经验!这是一个非常有趣的警告!非常感谢。