Инициализация нуля С++

У меня возникли проблемы с пониманием того, когда и почему именно член в моем классе инициализируется нулем в соответствии с 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.