Инициализация нуля С++
У меня возникли проблемы с пониманием того, когда и почему именно член в моем классе инициализируется нулем в соответствии с http://en.cppreference.com/w/cpp/language/zero_initialization.
Рассмотрим следующую тестовую программу:
#include <iostream>
#include <stdio.h>
class MyTest {
private:
const static unsigned int dimension = 8;
void (* myFunctions [dimension])();
public:
MyTest() {}
void print() {
for(unsigned int i=0; i < MyTest::dimension; i++) {
printf("myFunctions[%d] = %p\n", i, this->myFunctions[i]);
}
}
};
int main() {
//We declare and initialize an object on the stack
MyTest testObj = {};
testObj.print();
return 0;
}
Я объявляю класс иметь массив из 8 указателей на функции подписи "void functionname()". Когда я объявляю и инициализирую объект класса main
как MyTest testObj = {};
или MyTest testObj;
, Я ожидал, что это будет нулевой инициализацией, т.е. Все указатели являются нулевыми указателями.
Однако компиляция с g++ 5.3.0 на моем компьютере с Windows 10 с g++ -m32 -o test -std=c++14 test.cpp && test
machine дает результат:
myFunctions[0] = 76dd6b7d
myFunctions[1] = 00401950
myFunctions[2] = 0061ff94
myFunctions[3] = 004019ab
myFunctions[4] = 00401950
myFunctions[5] = 00000000
myFunctions[6] = 003cf000
myFunctions[7] = 00400080
Которые выглядят как неинициализированные значения из стека.
Если я перемещаю объявление объекта вне основного (как глобальную переменную), он снова печатает все нули.
Если я правильно понял cppreference, это связано с тем, что у меня есть переменная с продолжительностью статического хранения и, следовательно, нулевая инициализация. Он инициализирует мой тип класса нулевой инициализацией всех нестатических элементов данных моего класса (т. myFunctions
). Массив инициализируется нулевой инициализацией каждого его элемента, который в моем случае с указателем функции является нулевым указателем.
Почему это не ноль - инициализирует мой объект стек, когда я объявляю его с помощью MyTest testObj = {};
?
Ответы
Ответ 1
Следующие
MyTest testObj = {};
не является инициализацией нуля для MyTest
, но просто вызывает его конструктор по умолчанию. Страница cppreference объясняет, почему (внимание мое):
В составе последовательности инициализации значения для типов неклассов и для членов типов классов с инициализацией значения, у которых нет конструкторов, включая инициализацию значений элементов агрегатов, для которых не предусмотрены инициализаторы.
MyTest
- это тип класса, а a имеет конструктор.
Определение конструктора с
MyTest() = default;
вместо этого инициализирует объект.
Соответствующие стандартные котировки (акцент мой) ниже.
Из [dcl.init # 6]:
Для инициализации объекта типа T означает:
-
если T является (возможно, cv-квалифицированным) типом класса без конструктора по умолчанию ([class.ctor]) или конструктора по умолчанию, который предоставляется или удаляется пользователем, тогда объект инициализируется по умолчанию;
-
если T является (возможно, cv-qualit) типом класса без предоставленного пользователем или удаленного конструктора по умолчанию, тогда объект инициализируется нулем и семантические ограничения для инициализации по умолчанию проверяются, а если T имеет нетривиальный конструктор по умолчанию, объект инициализируется по умолчанию;
-
...
Из [dcl.init.list]:
Инициализация списка объекта или ссылки типа T определяется следующим образом:
-
...
-
В противном случае, если в списке инициализаторов нет элементов, а T - тип класса с конструктором по умолчанию, объект инициализируется значением.
Ответ 2
Вам нужно инициализировать этот переменный член, чтобы он работал как положено.
void (* myFunctions [dimension])();
В этом случае я предлагаю вам создать typedef
, хотя вы можете просто сделать это тоже:
void (* myFunctions [dimension])() = {};
Самостоятельное определение конструктора по умолчанию (как показано Витторио) не поможет.
Обратите внимание, что инициализация внутри класса доступна только с С++ 11. Кроме того, некоторые компиляторы могут испытывать трудности в некоторых случаях, если вы не используете typedef. В версии g++ 5.x шаблоны часто генерируют ошибки. В вашем случае, тем не менее, вы определяете массив, и {}
является практически единственным вариантом, который будет работать.
Как правильно проверить?
На всякий случай я написал простой тест, чтобы убедиться, что ответ Витторио был неверным.
Далее я впервые получаю нули, но это потому, что в памяти все нули, когда вы впервые выделяете ее (по крайней мере, в Unices). Тем не менее, во второй раз я выделить, я получаю 3.5 для a
m_c
и for()
цикл показывает ненулевые указатели.
Раскомментируйте = {}
внутри определения класса, и указатели зафиксированы, они показывают как ноль в первом и втором циклах for()
.
#include <iostream>
class A
{
public:
A() = default;
int m_a;
char m_b;
float m_c;
static constexpr unsigned int DIMENSION = 8;
void (* m_myFunctions [DIMENSION])() /*= {}*/ ;
};
int main(int argc, char * argv [])
{
A *a = new A;
std::cerr << "ptr = " << a << "\n";
std::cerr << "a = " << a->m_a << "\n";
std::cerr << "b = " << a->m_b << "\n";
std::cerr << "c = " << a->m_c << "\n";
for(unsigned int idx(0); idx < A::DIMENSION; ++idx)
{
std::cerr << "f[" << idx << "] = " << a->m_myFunctions[idx] << "\n";
a->m_myFunctions[idx] = (void (*)()) 0xffffffffffffffff;
}
a->m_a = 123;
a->m_b = 'c';
a->m_c = 3.5f;
std::cerr << "---- updated\n";
std::cerr << "a = " << a->m_a << "\n";
std::cerr << "b = " << a->m_b << "\n";
std::cerr << "c = " << a->m_c << "\n";
delete a;
std::cerr << "---- renewed\n";
a = new A;
std::cerr << "ptr = " << a << "\n";
std::cerr << "a = " << a->m_a << "\n";
std::cerr << "b = " << a->m_b << "\n";
std::cerr << "c = " << a->m_c << "\n";
for(unsigned int idx(0); idx < A::DIMENSION; ++idx)
{
std::cerr << "f[" << idx << "] = " << a->m_myFunctions[idx] << "\n";
}
a->m_a = 456;
a->m_b = 'z';
a->m_c = 8.531f;
std::cerr << "---- updated\n";
std::cerr << "a = " << a->m_a << "\n";
std::cerr << "b = " << a->m_b << "\n";
std::cerr << "c = " << a->m_c << "\n";
return 0;
}
Примечание: я использую new
потому что таким образом я могу доказать, что второй new A
возвращает тот же указатель, а оставшиеся данные отображаются во втором a
. Использование стека ничего не доказывает, и при первом запуске вашей программы стек также равен нулю.
Так что же происходит, когда вы пишете A a = {}
?
На странице, на которую вы ссылаетесь, есть пример с std::string()
который говорит что-то смешное:
std::string s; // zero-initialized to indeterminate value
// then default-initialized to ""
"Инициализируется нулями до неопределенного значения [s]"
Другими словами, он не инициализируется до тех пор, пока не будет вызван конструктор, и конструктор не выполнит свою работу. Другими словами, нулевая инициализация не работает для классов.
Интересующий пункт:
T t = {}; (2)
2) Как часть последовательности инициализации значения для типов, не относящихся к классам, и для членов типов классов, инициализированных значением, которые не имеют конструкторов, включая инициализацию значений элементов агрегатов, для которых не предусмотрены инициализаторы.
Инициализирует для не класса.
Для классов он инициализирует члены value-initialized.
Это также помогает с агрегатами (то есть массивами и структурами).
Так что если вы напишите:
class A { int value; };
Ничто не инициализируется.
Если вы напишите:
class A { int value = 123; };
Член переменной value
инициализируется нулем до значения 123
.