Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

In regard to C++ Standard:

  1. Does std::function of GNU Compiler Collection use union data type to cast between different function pointer types (e.g. to convert non-static member function pointer to non-member function pointer)? I think so. EDIT: It uses union data type but no cast is made (type-erasure).
  2. Is it an undefined behavior to cast between different function pointer types (in C++ or C++11 Standard)? I think so.
  3. Is it possible to implement a std::function without using any code which has an undefined behavior? I don't think so. I'm talking about this.

The following is my question:

Do we sometimes have to write code that has undefined behavior according to the C++ Standard (but they have defined behavior for particular C++ compilers such as GCC or MSVC)?

Does it mean that we can't/shouldn't prevent undefined behavior of our C++ codes?

like image 628
Sadeq Avatar asked Jul 09 '14 08:07

Sadeq


People also ask

What does undefined behavior mean in C?

When we run a code, sometimes we see absurd results instead of expected output. So, in C/C++ programming, undefined behavior means when the program fails to compile, or it may execute incorrectly, either crashes or generates incorrect results, or when it may fortuitously do exactly what the programmer intended.

Why does C have so much undefined behavior?

It exists because of the syntax rules of C where a variable can be declared without init value. Some compilers assign 0 to such variables and some just assign a mem pointer to the variable and leave just like that. if program does not initialize these variables it leads to undefined behavior.

What type of behavior C is undefined?

According to the C standards, signed integer overflow is undefined behaviour too. A few compilers may trap the overflow condition when compiled with some trap handling options, while a few compilers simply ignore the overflow conditions (assuming that the overflow will never happen) and generate the code accordingly.

What causes undefined behavior C++?

In C/C++ bitwise shifting a value by a number of bits which is either a negative number or is greater than or equal to the total number of bits in this value results in undefined behavior.


2 Answers

Nobody forces you to write anything, so nobody forces you to write code that invokes UB.

As for the standard library, its code is free to contain any nonportable behavior it wants - heck, it may even be written in another language with the compiler creating the bindings via magical unicorns; all that matters is that it behaves according to specification.

Come to think of it, it's obvious that at some level the standard library will have to go outside the standard - making syscalls, talking with hardware, ... is not even contemplated by the standard, and is often deeply platform-specific. For example, on 64 bit Linux you can perform syscalls with inline assembly (via the sysenter instruction) - the standard does not forbid this, it just doesn't mandate that every platform must behave like this.

As for the specific example, I don't see any UB - the unions there are used as specified by the standard - i.e. reading only from the last member you wrote into (hence the field m_flag).

like image 163
Matteo Italia Avatar answered Sep 29 '22 07:09

Matteo Italia


  1. Why is this ever interesting? It could be implemented in terms of __gnu_cplusplus_builtin_std_function__ for all we know.
  2. No, the standard explicitly permits that.
  3. Definitely yes, with any number of fully standard-conforming techniques.

The rest of the question is ill-posed, addressed in a comment.

Here's a rudimentary mock-up of std::function on the back of an envelope, with no casts or unions or AFAICT anything remotely dangerous. Of course not all features of real std::function either, but that's just a matter of some technical work.

#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); } 
like image 44
2 revs Avatar answered Sep 29 '22 07:09

2 revs