Подтвердить что ты не робот

Иногда нам приходится писать код с поведением undefined в соответствии со стандартом С++?

Что касается стандарта С++:

  • Использует ли std::function коллекцию компиляторов GNU использовать тип данных union для перевода между различными типами указателей функций (например, для преобразования нестатического указателя функции-члена в указатель функции нечлена)? Я думаю, что это так. EDIT: он использует тип данных union, но никакого приведения не производится (стирание стилей).
  • Можно ли отличать undefined behavior между различными типами указателей функций (в С++ или С++ 11 Standard)? Я так думаю.
  • Возможно ли реализовать std::function без использования какого-либо кода с undefined behavior? Я так не думаю. Я говорю об этом.

Следующий вопрос:

Мы иногда должны писать код с undefined behavior в соответствии со стандартом С++ (но у них есть defined behavior для определенных компиляторов С++, таких как GCC или MSVC)?

Означает ли это, что мы не можем/не должны предотвращать undefined behavior наших кодов С++?

4b9b3361

Ответ 1

Никто не заставляет вас писать что-либо, поэтому никто не заставляет вас писать код, вызывающий UB.

Что касается стандартной библиотеки, ее код может содержать любое непереносимое поведение, которое он хочет - черт, он может даже быть написан на другом языке, когда компилятор создает привязки через магические единороги; все, что имеет значение, заключается в том, что оно ведет себя согласно спецификации.

Подумайте об этом, очевидно, что на каком-то уровне стандартная библиотека должна выйти за рамки стандартных решений системных вызовов, разговаривать с аппаратными средствами,... даже не рассматривается стандартом и часто является глубоко платформенным, конкретный. Например, на 64-битном Linux вы можете выполнять системные вызовы с встроенной сборкой (с помощью команды sysenter) - стандарт не запрещает это, он просто не гарантирует, что каждая платформа должна вести себя так.

Что касается конкретного примера, я не вижу никакого UB - union, который используется, как указано стандартом, т.е. читается только из последнего члена, на который вы вписали (следовательно, поле m_flag).

Ответ 2

  • Почему это когда-нибудь интересно? Он может быть реализован в терминах __gnu_cplusplus_builtin_std_function__ для всех, что мы знаем.
  • Нет, стандарт явно разрешает это.
  • Определенно да, с любым количеством полностью стандартно-совместимых методов.

Остальная часть вопроса некорректна, рассматривается в комментарии.

Вот рудиментарный макет 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);
}

Ответ 3

Использует ли std:: function GNU Compiler Collection тип данных объединения для между различными типами указателей функций (например, для преобразования нестатический указатель функции-члена для указателя функции нечлена)? я так думайте.

Нет, он использует стирание типа. Это тип variant для объектов функции.

Является ли поведение undefined отличным от другого указателя функции типы (в С++ или С++ 11 Standard)? Я так думаю.

Это не обязательно, вы можете указать указатель на другой указатель на функцию и указатель функции-члена на другой указатель на функцию-член.

Можно ли реализовать функцию std:: без использования какого-либо кода который имеет поведение undefined? Я так не думаю. я говорю о это.

Да, вы можете, есть много примеров, это очень просто, так вы будете много рассказывать о С++:

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

Но со временем вы увидите, что способность думать и моделировать на высоком уровне важнее, чем знать детали конкретного языка.

Ответ 4

В некоторых редких случаях может быть произвольно использовать undefined -поведение, то есть когда выполняются все следующие условия:

  • вы уверены, что код не должен компилироваться на других платформах/с использованием других компиляторов (это происходит, но очень редко, поскольку вы действительно никогда не знаете, что будет с вашим кодом в будущем);
  • вы используете очень специфический компилятор с очень конкретной версией, с очень специфическими флагами компиляции, ориентированными на одну очень специфическую платформу (да, конечно...);
  • ваш компилятор в этих условиях укажет в нем документацию о том, что такое конкретный код undefined -behaviour, который у вас есть (например, он может вызывать terminate(), даже если стандарт говорит, что он undefined поведение или даже сделать что-то полезное, поскольку это разрешено undefined -behaviour со стандартной точки зрения);

В принципе, если ваш компилятор документирует поведение, когда стандарт говорит, что он undefined -bahaviour, вы можете положиться на него, если вы не пишете переносимый код.

Конечно, компиляторы, флаги компиляции, кодовые и целевые платформы меняются со временем, поэтому редко бывает хорошей идеей. Важно понять, что стандарт С++ определяет только то, что может выглядеть кросс-платформенный код на С++. Он не указывает больше (он не указывает реализацию, например) и просто укажет, где он не может указать переносимое поведение.

Поэтому, если вы пишете переносимый код или код, соответствующий стандарту, вам никогда не придется использовать поведение undefined.

Кроме того, большинство undefined поведение - это просто ошибки использования, которые дорого проверять во время выполнения (например, проверка границ массива, вы делаете это, если хотите, но стандарт не заставит компиляторы сделать это). Поэтому в некоторых случаях полезно добавить проверки, чтобы избежать поведения undefined, но лучше просто не допускать, чтобы этот код был написан в начале. Это одна из причин, по которой сильная проверка типов полезна для больших кодовых баз, а также почему статический анализ получает много внимания в наши дни. Они помогают предотвратить даже компиляцию некоторого кода, который может быть проверен во время компиляции, что они будут проблематичными.

Ответ 5

Стандартная библиотека С++ является частью реализации. Таким образом, стандартная библиотека С++ может содержать исходный код, который будет иметь поведение undefined, если вы написали его в своем собственном коде пользователя, но реализация гарантирует, что он работает, как определено стандартом С++. Если это не работает как определено, это не поведение undefined, это ошибка в реализации.

Код не просто имеет поведение "undefined", он имеет "поведение, которое не определено стандартом С++". Например, есть тонны функций Posix, которые определяются стандартом Posix. Если в вашей реализации говорится, что "эта реализация соответствует стандарту С++ и стандарту Posix", то использование функций Posix, которые не имеют поведения, определенного стандартом С++, прекрасно, потому что они определили поведение в вашей реализации (возможно, не на другом, не совместимы с Posix).

И вы, возможно, слышали, что поведение undefined может форматировать ваш жесткий диск (и другие неприятные вещи). Но наоборот, поскольку "форматирование жестких дисков" нигде не упоминается в стандарте С++, если форматирование вашего жесткого диска - это то, что ваша программа действительно должна делать, тогда вам нужно будет выполнить что-то, что соответствует undefined к стандарту С++.

Очевидно, вам понадобится довольно веская причина для вызова поведения undefined в вашем коде. Из-за очевидных опасностей (возможно различное поведение, возможное поведение при оптимизации компиляторов, огромные проблемы с переносимостью), любое поведение undefined без особого обоснования - очень плохой знак.

Ответ 6

Каждая программа, использующая POSIX API для доступа к динамически связанным функциям, должна полагаться на поведение undefined по стандарту С++: в частности, преобразование void*, возвращаемое dlsym для указателя функции. Конечно, "работает как ожидалось" - это один из способов реализации поведения undefined, а стандарт POSIX предусматривает, что листинг хорошо определен на всех совместимых платформах.

Ответ 7

В самых быстрых реализациях быстрого обратного квадратного корня реализовано поведение undefined. Для более совместимой реализации может потребоваться дополнительная копия . Учитывая, что смысл прямого обратного квадратного корня заключается в том, что он быстрый, это может просто квалифицироваться как "необходимость" в зависимости от ситуации. Однако современные оптимизаторы способны на такое колдовство, что я не удивлюсь, если совместимая версия будет спокойно преобразована в оптимальную форму.