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

Является ли эта инициатива инициализации структуры С++ безопасной?

Вместо того, чтобы помнить, чтобы инициализировать простую структуру "C", я мог бы извлечь из нее и обнулить ее в конструкторе следующим образом:

struct MY_STRUCT
{
    int n1;
    int n2;
};

class CMyStruct : public MY_STRUCT
{
public:
    CMyStruct()
    {
        memset(this, 0, sizeof(MY_STRUCT));
    }
};

Этот трюк часто используется для инициализации структур Win32 и иногда может устанавливать вездесущий элемент cbSize.

Теперь, если для вызова memset для уничтожения нет таблицы виртуальных функций, это безопасная практика?

4b9b3361

Ответ 1

Преамбула:

В то время как мой ответ по-прежнему хорошо, я нахожу litb answer, намного превосходящий мой, потому что:

  • Он учит меня трюку, который я не знал (ответы на лабле, как правило, имеют этот эффект, но это первый раз, когда я его записываю)
  • Он отвечает именно на вопрос (то есть инициализирует исходную часть структуры до нуля)

Так что, пожалуйста, подумайте о лифте перед моим. На самом деле, я предлагаю автору вопроса рассмотреть вопрос о литовском ответе как о правильном.

Оригинальный ответ

Помещение истинного объекта (т.е. std::string) и т.д. внутри будет ломаться, потому что истинный объект будет инициализирован до memset, а затем перезаписан нулями.

Использование списка инициализации не работает для g++ (я удивлен...). Инициализируйте его в теле конструктора CMyStruct. Это будет С++ дружественным:

class CMyStruct : public MY_STRUCT
{
public:
    CMyStruct() { n1 = 0 ; n2 = 0 ; }
};

P.S.: Я предположил, что у вас действительно есть no контроль над MY_STRUCT. С контролем вы бы добавили конструктор непосредственно в MY_STRUCT и забыли о наследовании. Обратите внимание, что вы можете добавлять не виртуальные методы в C-подобную структуру и все еще вести себя как структура.

EDIT: Добавлена ​​отсутствующая скобка, после комментария Лу Франко. Спасибо!

EDIT 2: я пробовал код на g++, и по какой-то причине использование списка инициализации не работает. Я исправил код, используя конструктор body. Решение остается в силе, однако.

Пожалуйста, переоцените мой пост, поскольку исходный код был изменен (см. журнал изменений для получения дополнительной информации).

РЕДАКТИРОВАТЬ 3: После прочтения комментария Роба, я думаю, у него есть точка, достойная обсуждения: "Согласовано, но это может быть огромная структура Win32, которая может измениться с помощью нового SDK, поэтому memset является будущим доказательством".

Я не согласен: знай Microsoft, это не изменится из-за их потребности в идеальной обратной совместимости. Они создадут вместо этого расширенную структуру MY_STRUCT Ex с тем же первоначальным расположением, что и MY_STRUCT, с добавочными членами в конце и распознаваемыми с помощью переменной члена "size", такой как структура, используемая для RegisterWindow, IIRC.

Таким образом, единственная действительная точка, остающаяся от комментария Роба, - это "огромная" структура. В этом случае, возможно, memset более удобен, но вы должны сделать MY_STRUCT переменным членом CMyStruct вместо его наследования.

Я вижу еще один хак, но я думаю, что это сломается из-за возможной проблемы выравнивания структуры.

EDIT 4: Пожалуйста, взгляните на решение Фрэнка Крюгера. Я не могу пообещать, что это портативный (я думаю, это так), но он по-прежнему интересен с технической точки зрения, потому что он показывает один случай, когда на С++ адрес указателя "this" "переходит" от базового класса к его унаследованному классу.

Ответ 2

Вы можете просто инициализировать базу значений, и все ее члены будут нулевыми. Это гарантировано

struct MY_STRUCT
{
    int n1;
    int n2;
};

class CMyStruct : public MY_STRUCT
{
public:
    CMyStruct():MY_STRUCT() { }
};

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

Нет противного memset для этого. Это не гарантировало, что memset работает в вашем коде, хотя он должен работать на практике.

Ответ 3

Гораздо лучше, чем memset, вы можете использовать этот маленький трюк:

MY_STRUCT foo = { 0 };

Это приведет к инициализации всех членов до 0 (или их значения по умолчанию iirc), не нужно указывать значение для каждого.

Ответ 4

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

memset(static_cast<MY_STRUCT*>(this), 0, sizeof(MY_STRUCT));

Я уверен, что ваше решение будет работать, но я сомневаюсь, что при смешивании memset и классов есть какие-то гарантии.

Ответ 5

Это прекрасный пример переноса идиомы C на С++ (и почему это может не всегда работать...)

Проблема с использованием memset заключается в том, что в С++ структура и класс - это одно и то же, кроме того, что по умолчанию структура имеет общедоступную видимость, а класс имеет конфиденциальную видимость.

Таким образом, что, если позже, какой-то хорошо программирующий программист меняет MY_STRUCT так:


struct MY_STRUCT
{
    int n1;
    int n2;

   // Provide a default implementation...
   virtual int add() {return n1 + n2;}  
};

Добавив эту единственную функцию, ваш memset может теперь вызвать хаос. Подробное обсуждение в comp.lang.c+

Ответ 6

В примерах есть "неуказанное поведение".

Для не-POD порядок, с помощью которого компилятор выставляет объект (все базовые классы и члены), не указан (ISO С++ 10/3). Рассмотрим следующее:

struct A {
  int i;
};

class B : public A {       // 'B' is not a POD
public:
  B ();

private:
  int j;
};

Это может быть указано как:

[ int i ][ int j ]

Или как:

[ int j ][ int i ]

Поэтому использование memset непосредственно по адресу 'this' - это очень неуказанное поведение. Один из ответов выше, на первый взгляд, выглядит более безопасным:

 memset(static_cast<MY_STRUCT*>(this), 0, sizeof(MY_STRUCT));

Я считаю, что, строго говоря, это приводит к неуказанному поведению. Я не могу найти нормативный текст, однако в примечании в 10/5 говорится: "Субобъект базового класса может иметь макет (3.7), отличный от макета самого производного объекта того же типа".

В результате компилятор я мог выполнять оптимизацию пространства с разными членами:

struct A {
  char c1;
};

struct B {
  char c2;
  char c3;
  char c4;
  int i;
};

class C : public A, public B
{
public:
  C () 
  :  c1 (10);
  {
    memset(static_cast<B*>(this), 0, sizeof(B));      
  }
};

Может быть выложено как:

[ char c1 ] [ char c2, char c3, char c4, int i ]

В 32-битной системе из-за смещения и т.д. для "B" sizeof (B), скорее всего, будет 8 байтов. Однако sizeof (C) также может быть "8" байтами, если компилятор упаковывает данные. Поэтому вызов memset может перезаписать значение, указанное в 'c1'.

Ответ 7

Точная компоновка класса или структуры не гарантируется в С++, поэтому вы не должны делать предположений о ее размере извне (это означает, что вы не являетесь компилятором).

Возможно, это работает, пока вы не найдете компилятор, на котором это не так, или вы выбросите в таблицу какую-нибудь vtable.

Ответ 8

Если у вас уже есть конструктор, почему бы просто не инициализировать его там, где n1 = 0; п2 = 0; - это, безусловно, более нормальный путь.

Изменить: На самом деле, как показал paercebal, инициализация ctor еще лучше.

Ответ 9

Мое мнение - нет. Я не уверен, что он получает.

По мере изменения вашего определения CMyStruct и добавления/удаления членов это может привести к ошибкам. Легко.

Создайте конструктор для CMyStruct, который принимает MyStruct, имеет параметр.

CMyStruct::CMyStruct(MyStruct &)

Или что-то от этого искалось. Затем вы можете инициализировать публичный или закрытый элемент "MyStruct".

Ответ 10

С точки зрения ISO С++ есть две проблемы:

(1) Является ли объект POD? Аббревиатура обозначает Plain Old Data, а в стандарте перечислены то, что вы не можете иметь в POD (у Википедии есть хорошее резюме). Если это не POD, вы не можете его изменить.

(2) Существуют ли члены, для которых все биты-ноль недействительны? В Windows и Unix указатель NULL - это все биты нуль; это не должно быть. Плавающая точка 0 имеет все биты нуля в IEEE754, что довольно часто, и на x86.

Фрэнк Крюгерс обсуждает ваши проблемы, ограничивая memset базой POD класса, отличного от POD.

Ответ 11

Попробуйте это - перегрузите новый.

EDIT: я должен добавить: это безопасно, потому что память обнуляется перед вызовом любых конструкторов. Большая ошибка - работает только если объект динамически распределен.

struct MY_STRUCT
{
    int n1;
    int n2;
};

class CMyStruct : public MY_STRUCT
{
public:
    CMyStruct()
    {
        // whatever
    }
    void* new(size_t size)
    {
        // dangerous
        return memset(malloc(size),0,size);
        // better
        if (void *p = malloc(size))
        {
            return (memset(p, 0, size));
        }
        else
        {
            throw bad_alloc();
        }
    }
    void delete(void *p, size_t size)
    {
        free(p);
    }

};

Ответ 12

Если MY_STRUCT является вашим кодом, и вы довольны использованием компилятора С++, вы можете поместить конструктор туда без упаковки в класс:

struct MY_STRUCT
{
  int n1;
  int n2;
  MY_STRUCT(): n1(0), n2(0) {}
};

Я не уверен в эффективности, но я ненавижу делать трюки, когда вам не нужна эффективность.

Ответ 13

Комментарий litb answer (кажется, мне еще не разрешено прокомментировать):

Даже с помощью этого приятного решения в стиле С++ вы должны быть очень осторожны, чтобы вы не применили это наивно к структуре, содержащей член не-POD.

Некоторые компиляторы затем не инициализируются правильно.

См. этот ответ на аналогичный вопрос. У меня лично был плохой опыт на VC2008 с дополнительным std::string.

Ответ 14

Это немного кода, но он многоразовый; включите его один раз, и он должен работать для любого POD. Вы можете передать экземпляр этого класса любой функции, ожидающей MY_STRUCT, или использовать функцию GetPointer, чтобы передать ее в функцию, которая изменит структуру.

template <typename STR>
class CStructWrapper
{
private:
    STR MyStruct;

public:
    CStructWrapper() { STR temp = {}; MyStruct = temp;}
    CStructWrapper(const STR &myStruct) : MyStruct(myStruct) {}

    operator STR &() { return MyStruct; }
    operator const STR &() const { return MyStruct; }

    STR *GetPointer() { return &MyStruct; }
};

CStructWrapper<MY_STRUCT> myStruct;
CStructWrapper<ANOTHER_STRUCT> anotherStruct;

Таким образом, вам не нужно беспокоиться о том, являются ли NULL все 0 или представлениями с плавающей запятой. Пока STR является простым агрегатным типом, все будет работать. Когда STR не является простым агрегатным типом, вы получите ошибку времени компиляции, поэтому вам не придется беспокоиться о том, чтобы случайно использовать это. Кроме того, если тип содержит что-то более сложное, если у него есть конструктор по умолчанию, вы в порядке:

struct MY_STRUCT2
{
    int n1;
    std::string s1;
};

CStructWrapper<MY_STRUCT2> myStruct2; // n1 is set to 0, s1 is set to "";

С другой стороны, это медленнее, так как вы делаете дополнительную временную копию, и компилятор назначит каждому члену по отдельности, вместо одного memset.

Ответ 15

Я использую агрегатную инициализацию, но только указываю инициализаторы для членов, которых интересует, например:

STARTUPINFO si = {
    sizeof si,      /*cb*/
    0,              /*lpReserved*/
    0,              /*lpDesktop*/
    "my window"     /*lpTitle*/
};

Остальные члены будут инициализированы нулями соответствующего типа (как в столбце Drealmer). Здесь вы доверяете Microsoft не безвозмездно нарушать совместимость, добавляя в нее новых членов структуры (разумное предположение). Это решение кажется мне оптимальным - один оператор, никакие классы, не memset, никаких предположений о внутреннем представлении нулей с плавающей точкой или нулевых указателях.

Я думаю, что взломы с наследованием - ужасный стиль. Публичное наследование означает IS-A для большинства читателей. Также обратите внимание, что вы наследуете класс, который не предназначен для базы. Поскольку виртуального деструктора нет, клиенты, которые удаляют экземпляр производного класса с помощью указателя на базу, будут ссылаться на поведение undefined.

Ответ 16

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

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

#include <stdio.h>

#define MY_STRUCT(x) MY_STRUCT x = {0}

struct MY_STRUCT
{
    int n1;
    int n2;
};

int main(int argc, char *argv[])
{
    MY_STRUCT(s);

    printf("n1(%d),n2(%d)\n", s.n1, s.n2);

    return 0;
}