admin管理员组

文章数量:1794759

C++属性——noreturn

自C++11以来,C++引入了很多属性,恰当的使用属性可以辅助编译器进行更多的优化,从而提高程序的性能,反之,可能会导致程序崩溃或产生未定义行为。本文将结合实例代码讲解如何正确使用noreturn属性,以确保程序的正确性和稳定性。

1. 背景

小王看到同事的代码中有部分函数被标记为noreturn,感觉挺高大上的,然后就自己模仿之,在自己的代码中也加入了相对应的标记,代码可简化如下:

代码语言:javascript代码运行次数:0运行复制
#pragma once
#include<string>
class People 
{
public:
  People(std::string name, int age):m_name(name),m_age(age){
  }
  [[noreturn]] void setName(std::string name)
{
  m_name = name;
  }
  [[noreturn]] void setAge(int age)
{
  m_age = age;
  }
private:
  std::string m_name;
  int m_age;
};

他想当然的认为noreturn属性用于标记没有返回值/或返回值为void的函数,并且自测环节(debug模式下)没出现任何问题,他还蛮开心的,又学到了一个新的知识点。

可是发布Release版本时,程序总是崩溃,排查很久也没有找到原因。小王将所有注意力集中于debug和release两种模式的区别,最后发现,在debug模式下打开优化,也会存在崩溃。进而进一步确认是开启优化后,编译器优化导致的程序崩溃。

秉承着编译器没错,自己代码有错的原则,进一步分析自己的代码,将noreturn属性删除后,程序不再崩溃。

综合如上分析可知,当函数被标记为noreturn时,一旦开启优化,可能会导致程序崩溃。

2. 走近noreturn

cppreference中对于noreturn的表述如下:noreturn 是C++11引入的一种属性,用于告诉编译器某个函数不会返回到调用者。此属性仅用于函数声明中所声明的函数名,若拥有此属性的函数返回,则行为未定义。

如上可知,noreturn标记的是函数不会返回给调用者,并不是函数没有返回值。如果函数返还给调用者,则属于未定义行为。怎样才是不会返回给调用者呢?存在如下几种场景:

  • 程序终止:exit()、等函数
  • 抛出异常:throw 语句
  • 死循环:while(true) {} 等循环

当开启优化时,由于noreturn属性的存在,导致编译器认为该函数不会返还给调用者便进行了部分优化,例如移除某些不必要的清理代码或跳过函数返回后的执行路径,进而使得程序呈现在未开启优化时运行正常,而开启优化时程序崩溃。

3. 代码示例

由于noreturn属性仅使用函数不会返还给调用者的场景,所以noreturn属性的使用场景并不多。给出简单的代码示例如下:

代码语言:javascript代码运行次数:0运行复制
#include <iostream>
#include <cstdlib> // 用于exit()
//终止程序
[[noreturn]] void terminateProgram() {
std::cerr << "Fatal error occurred. Exiting program." << std::endl;
std::exit(1); // 终止程序
}
//抛出异常
[[noreturn]] void throwException() {
throw std::runtime_error("An error occurred!");
}
//死循环
[[noreturn]] void infiniteLoop() {
while (true) {}
}

实际使用过程,可能并不简单的是如上的函数形式,也可以是成员函数等多种形式。

如上为正确使用noreturn的场景,反之,如下为错误示例代码

代码语言:javascript代码运行次数:0运行复制
#include <iostream>
// 错误地标记了[[noreturn]]
[[noreturn]] void potentiallyReturn(bool condition) {
  if (condition) {
  std::cout << "Exiting program..." << std::endl;
  std::exit(1); // 正确处理:程序终止
  } else {
  std::cout << "Returning to caller" << std::endl;
  return; // 违反[[noreturn]]属性
  }
}
int main() {
  potentiallyReturn(false); // 这个调用会导致未定义行为
  std::cout << "This line may not execute correctly in release mode." << std::endl;
}

在上述代码中,函数potentiallyReturn在某些情况下会返回给调用者,但它却被标记为noreturn。这种错误使用可能会导致编译器在优化时跳过return路径的清理工作,进而在release模式下引发崩溃或未定义行为。

4.noreturn使用原则

为了避免错误地使用noreturn属性,建议遵循以下原则:

  • 确保函数无论如何都不会返回:仅在函数通过抛出异常或调用诸如exit()、abort()等永远不会返回的函数时,才使用noreturn。
  • 审查函数的所有执行路径:在标记函数为noreturn之前,仔细检查函数的所有可能执行路径。如果任何路径有返回的可能性,禁止使用该属性。
  • 避免滥用属性进行优化:noreturn虽然可以在某些情况下帮助编译器进行优化,但它并不是提升性能的主要手段。优化应以程序的正确性为前提,不应因为小幅度性能提升而冒险使用noreturn。
  • 在release模式下仔细测试:因为release模式下开启了更多的编译器优化,所以在该模式下要进行充分的测试。即使在debug模式下一切正常,也不意味着release模式就不会暴露问题。

5. 总结

noreturn 是C++中的一个重要属性,但也容易引发误用。它并不意味着函数没有返回值,而是表示函数不会返回控制权给调用者。误用noreturn 可能导致编译器在release模式下进行错误优化,进而导致程序崩溃或产生未定义行为。

在使用noreturn时,务必确保函数在任何执行路径上都不会返回调用点。谨慎使用这一属性可以避免不必要的调试和崩溃风险。最后,编写高质量的代码和进行充分的测试是避免此类问题的关键。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。原始发表:2024-10-24,如有侵权请联系 cloudcommunity@tencent 删除程序函数优化c++编译器

本文标签: C属性noreturn