2015-10-28 2 views
14

я наткнулся на интересных правилах безопасного кодирования в C++, который гласит:C++ код с неопределенным поведением, компилятор генерирует станд :: исключение

Do not reenter a function during the initialization of a static variable declaration. If a function is reentered during the constant initialization of a static object inside that function, the behavior of the program is undefined. Infinite recursion is not required to trigger undefined behavior, the function need only recur once as part of the initialization.

non_compliant пример то же:

#include <stdexcept> 

int fact(int i) noexcept(false) { 
    if (i < 0) { 
    // Negative factorials are undefined. 
    throw std::domain_error("i must be >= 0"); 
    } 

    static const int cache[] = { 
    fact(0), fact(1), fact(2), fact(3), fact(4), fact(5), 
    fact(6), fact(7), fact(8), fact(9), fact(10), fact(11), 
    fact(12), fact(13), fact(14), fact(15), fact(16) 
    }; 

    if (i < (sizeof(cache)/sizeof(int))) { 
    return cache[i]; 
    } 

    return i > 0 ? i * fact(i - 1) : 1; 
} 

которые в соответствии с источником выдает ошибку:

terminate called after throwing an instance of '__gnu_cxx::recursive_init_error' 
    what(): std::exception 

при выполнении в Visual Studio 2013. Я попробовал аналогичный код и получил ту же ошибку (скомпилирован с использованием g ++ и выполнен, на Ubuntu).

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

Что является лучшим объяснением этого?

+4

Я правильно понимаю вас: вы хотите, чтобы зависящая от реализации причина, почему этот определенный случай неопределенного поведения ведет себя определенным образом? – Downvoter

+0

@cad: вы правы! .... :) –

ответ

17

Чтобы выполнить fact(), вам необходимо сначала инициализировать статически fact::cache[]. Для того, чтобы изначально fact::cache, вам необходимо выполнить fact(). Там есть круговая зависимость, которая ведет к поведению, которое вы видите. cache будет инициализироваться только один раз, но для инициализации он должен быть инициализирован. Даже набрав это, моя голова вращается.

Правильный способ ввести кэш таблицы, как это не разделять его на другую функцию:

int fact(int i) noexcept(false) { 
    if (i < 0) { 
    // Negative factorials are undefined. 
    throw std::domain_error("i must be >= 0"); 
    } 

    return i > 0 ? i * fact(i - 1) : 1; 
} 

int memo_fact(int i) noexcept(false) { 
    static const int cache[] = { 
    fact(0), fact(1), fact(2), fact(3), fact(4), fact(5), 
    fact(6), fact(7), fact(8), fact(9), fact(10), fact(11), 
    fact(12), fact(13), fact(14), fact(15), fact(16) 
    }; 

    if (i < (sizeof(cache)/sizeof(int))) { 
    return cache[i]; 
    } 
    else { 
    return fact(i); 
    }  
} 

Здесь memo_fact::cache[] будет инициализирован только один раз - но его инициализации больше не зависит от себя. Поэтому у нас нет проблем.

+0

спасибо за объяснение :) .. Мое понимание из этого состоит в том, что массив кэша, который должен быть статически инициализирован один раз, повторно повторно инициализируется из-за его зависимости от рекурсивного вызова функции fact(). Поэтому эта функция повторно вводит свою инициализацию. –

6

C++ стандарт, §6.7/4, говорит следующее о инициализации блока-Scope переменных со статической продолжительностью хранения:

If control re-enters the declaration recursively while the variable is being initialized, the behavior is undefined.

Ниже информативный пример приведен:

int foo(int i) { 
static int s = foo(2*i); // recursive call - undefined 
return i+1; 
} 

Это относится и к вашему примеру. fact(0) - это рекурсивный вызов, поэтому декларация cache вводится повторно. Вызывается неопределенное поведение.

Важно помнить, что означает неопределенное поведение. Неопределенное поведение означает, что может произойти все, что есть, и «все» вполне естественно включает исключения.

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

+0

спасибо за объяснение :) ... –

Смежные вопросы