我们是否有时必须根据c++标准编写具有未定义行为的代码?

Do we sometimes have to write code that has undefined behavior according to the C++ Standard?

本文关键字:未定义 代码 标准 是否 c++ 我们      更新时间:2023-10-16

关于c++标准:

  1. GNU编译器集合的std::function是否使用union数据类型在不同的函数指针类型之间进行转换(例如将非静态成员函数指针转换为非成员函数指针)?
  2. 我想是的。 EDIT:它使用union数据类型,但不进行强制转换(类型擦除)
  3. 是一个undefined behavior之间转换不同的函数指针类型(在c++或c++ 11标准)?我想是的。
  4. 有没有可能实现一个std::function不使用任何代码有一个undefined behavior ?
  5. 我不这么认为。我说的是这个

我的问题是:

我们是否有时必须根据c++标准编写具有undefined behavior的代码(但他们为特定的c++编译器(如GCC或MSVC)具有defined behavior)?

这是否意味着我们不能/不应该阻止c++代码中的undefined behavior ?

没有人强迫你写任何东西,所以没有人强迫你写调用UB的代码。

至于标准库,它的代码可以自由地包含任何它想要的不可移植行为——见鬼,它甚至可以用另一种语言编写,由编译器通过神奇的独角兽创建绑定;重要的是它的行为符合规范。

仔细想想,很明显,在某种程度上,标准库将不得不超出标准-进行系统调用,与硬件通信,…标准甚至没有考虑到这一点,而且通常是平台特定的。例如,在64位Linux上,您可以使用内联汇编(通过sysenter指令)执行系统调用——标准并不禁止这样做,它只是没有强制要求每个平台都必须这样做。

至于具体的例子,我没有看到任何UB - union s按照标准指定的方式使用-即仅从您写入的最后一个成员读取(因此字段m_flag)。

    为什么这很有趣?据我们所知,它可以根据__gnu_cplusplus_builtin_std_function__来实现。
  1. 不,标准明确允许。
  2. 当然可以,使用任何完全符合标准的技术。

问题的其余部分是不恰当的,在评论中解决。

这是一个信封背面的std::function的基本模型,没有强制类型转换或联合或AFAICT任何危险的东西。当然不是真正的std::function的所有功能,但这只是一些技术工作的问题。

#include <memory>
#include <iostream>
#include <type_traits>
template <typename R, typename ... Args>
struct CallBase
{
  virtual R operator()(Args... args) = 0;
  virtual ~CallBase() {}
};
template <typename R, typename ... Args>
struct FunCall : CallBase<R, Args...>
{
  virtual R operator()(Args... args) { return f(args...); }
  R(*f)(Args...);
  FunCall(R f(Args...)) : f(f) {}
};
template <typename Obj, typename R, typename ... Args>
struct ObjCall : CallBase<R, Args...>
{
  virtual R operator()(Args... args) { return o(args...); }
  Obj o;
  ObjCall(Obj o) : o(o) {}
};
template <typename R, typename ... Args> struct MemFunCall;
template <typename R, typename Cl, typename ... Args>
struct MemFunCall<R, Cl, Args...> : CallBase<R, Cl, Args...>
{
  typedef typename std::remove_reference<Cl>::type Rcl;
  virtual R operator()(Cl c, Args... args) { return (c.*f)(args...); }
  R (Rcl::*f)(Args...);
  MemFunCall(R (Rcl::*f)(Args...)) : f(f) {}
};

template <typename Fn> class Function;
template <typename R> struct Function<R()>
{
  std::unique_ptr<CallBase<R>> fn;
  R operator()() { return (*fn)(); }
  Function(R (*f)()) : fn(new FunCall<R>(f)) {}
  template<typename Obj>
  Function(Obj o) : fn(new ObjCall<Obj, R>(o)) {}
};
template <typename R, typename Arg1, typename ... Args> 
struct Function<R(Arg1, Args...)>
{
  std::unique_ptr<CallBase<R, Arg1, Args...>> fn;
  R operator()(Arg1 arg1, Args... args) { return (*fn)(arg1, args...); }
  Function(R (*f)(Arg1 arg1, Args...)) :
    fn(new FunCall<R, Arg1, Args...>(f)) {}
  template<typename T>
  Function(R (T::*f)(Args...)) : 
    fn(new MemFunCall<R, Arg1, Args...>(f)) {}
  template<typename Obj>
  Function(Obj o) : fn(new ObjCall<Obj, R, Arg1, Args...>(o)) {}
};
struct Foo
{
  static void bar (int a) { std::cout << "bar " << a << std::endl; }
  int baz (const char* b) { std::cout << "baz " << b << std::endl; return 0; }
  void operator()(double x) { std::cout << "operator() " << x << std::endl; }
};
int main ()
{
  Function<void(int)> f1(&Foo::bar);
  f1(3);
  Foo foo;
  Function<int(Foo&, const char*)> f2(&Foo::baz);
  f2(foo, "whatever");
  Function<void(double)> f3(foo);
  f3(2.75);
}

GNU Compiler Collection的std::函数是否使用联合数据类型to在不同的函数指针类型之间强制转换(例如转换非静态成员函数指针指向非成员函数指针)?我这么认为。

不,它使用类型擦除。它是函数对象的一种variant

在不同的函数指针之间强制转换是否是未定义的行为类型(c++或c++ 11标准)?我想是的。

可以将函数指针强制转换为另一个函数指针,也可以将成员函数指针强制转换为另一个成员函数指针。

是否有可能不使用任何代码就实现std::函数哪个有未定义的行为?我不这么想。我说的是这个。

是的,你可以,有很多例子在那里,这真的很容易,这样做会教会你很多关于c++的知识:

http://probablydance.com/2013/01/13/a-faster-implementation-of-stdfunction/https://codereview.stackexchange.com/questions/14730/impossibly-fast-delegate-in-c11http://avdgrinten.wordpress.com/2013/08/07/c-stdfunction-with-the-speed-of-a-macro/

但是,随着时间的推移,你会发现在高层次上思考和建模的能力比了解特定语言的细节更重要。

在某些罕见的情况下,可以自愿利用未定义的行为,即在满足以下所有条件的情况下:

  • 您确定代码不应该在其他平台上/使用其他编译器进行编译(这种情况发生但非常罕见,因为您真的不知道将来会发生什么);
  • 您正在使用一个非常特定的编译器,具有非常特定的版本,具有针对一个非常特定的平台的非常特定的编译标志(是的,当然…);
  • 你的编译器,在这些条件下,在它的文档中指定你手头的特定未定义行为代码的行为(例如,即使标准说它是未定义行为,它也可能调用terminate(),或者甚至做一些有用的事情,因为从标准的角度来看,它是由未定义行为允许的);

基本上,如果您的编译器确实记录了标准规定为未定义行为的行为,那么如果您不编写可移植代码,则可以依赖它。

当然,编译器、编译标志、代码和目标平台会随着时间的推移而变化,所以这不是一个好主意。重要的是要理解c++标准只定义了跨平台c++代码的样子。它没有指定更多(例如,它没有指定实现),只是指定了它不能指定可移植行为的地方。

因此,如果您编写可移植代码,或遵循标准的代码,则永远不必利用未定义行为。

而且,大多数未定义的行为只是在运行时检查成本很高的使用错误(比如检查数组边界,如果你想的话,你可以这样做,但标准不会强迫编译器这样做)。因此,在某些情况下,添加检查以避免未定义行为可能是有用的,但最好一开始就不允许编写该代码。这就是强类型检查对大型代码库很有帮助的原因之一,也是静态分析最近受到很多关注的原因之一。它们确实有助于防止编译一些代码,这些代码可以在编译时检查它们是否有问题。

c++标准库是实现的一部分。因此,c++标准库可能包含在您自己的用户代码中编写的具有未定义行为的源代码,但实现保证它按照c++标准的定义工作。如果它没有按定义工作,那不是未定义行为,那是实现中的错误。

代码不只是有"未定义的行为",它有"未被c++标准定义的行为"。例如,有大量Posix函数是由Posix标准定义的。如果你的实现说"这个实现遵循c++标准和Posix标准",那么使用没有c++标准定义的行为的Posix函数是可以的,因为它们在你的实现上定义了行为(可能不是在另一个不兼容Posix的实现上)。

你可能听说过未定义的行为可以格式化你的硬盘(和其他讨厌的东西)。但另一方面,由于"格式化硬盘"在c++标准中没有任何地方提到,如果格式化硬盘是你的程序实际上应该做的事情,那么你将不得不做一些根据c++标准未定义的行为。

显然你需要一些很好的理由在你的代码中调用未定义的行为。由于存在明显的危险(可能出现不同的行为,优化编译器可能出现令人讨厌的行为,巨大的可移植性问题),任何没有强有力理由的未定义行为都是一个非常糟糕的迹象。

快速平方根反比的最快速的表面值实现涉及未定义行为。更兼容的实现可能需要额外的副本。考虑到快速平方根反比être的原因在于快速,根据情况,这可能只是"需要"。然而,现代优化器有这样的能力,如果兼容版本被悄悄地转化为最优形式,我不会感到惊讶。

每个使用POSIX API访问动态链接函数的程序必须依赖于c++标准未定义的行为:特别是将dlsym返回的void*转换为函数指针。当然,"按预期工作"是实现未定义行为的一种方式,POSIX标准要求在所有兼容的平台上都定义良好的类型转换。

相关文章: