Операция new с размещением

Доброго времени суток всем!
Есть такой код, в котором используется статический массив в качестве буфера памяти для размещения в нем массива структур.

   // операция new с размещением

#include <iostream>
#include <new>                  // new с размещением

const int STRUCTSIZE = 2;       // размер массива структур
const int BUFFSIZE = 1;         // размер массива buffer
char buffer[BUFFSIZE];          // блок памяти под буфер

struct chaff{                   // определение структуры
    char dross[20];
    int slag;
};

int main()
{
    chaff matrix[STRUCTSIZE] = {        // инициализация массива структур
        {"Blablabla", 777},
        {"Lalala", 555}
    };

    std::cout << "Size of buffer: " << sizeof buffer << " bytes\n"; // размер буфера
    std::cout << "Size of matrix: " << sizeof matrix << " bytes\n"; // размер массива структур

    chaff * pchaff = new (buffer) chaff[STRUCTSIZE];        // выделение памяти с размещением
                                                        // в массиве buffer

    pchaff = matrix;                // присвоение указателю адреса массива структур matrix

    for (int i = 0; i < STRUCTSIZE; i++){       // отображение массива структур
        std::cout << pchaff[i].dross << "\n"
            << pchaff[i].slag << "\n";
    }

    return 0;
}

Вывод программы такой:

 Size of buffer: 1 bytes
 Size of matrix: 48 bytes
 Blablabla
 777
 Lalala
 555

Вопрос:
Если размер буфера 1 байт то куда размещается массив структур matrix размером в 48 байт?

Ответ:
Массив структур matrix размером в 48 байт никуда не размещается. В этом операторе:

chaff * pchaff = new (buffer) chaff[STRUCTSIZE];        // выделение памяти с размещением

выделения памяти не происходит (см. третью форму new). Перед эти оператором память должна быть уже выделена в buffer.

Далее — цепь счастливых случайностей: (1) ты не пишешь в структуры, которые были «выделены» в buffer; (2) ты переводишь указатель pchaff на другой адрес, из которого и читаешь информацию (pchaff = matrix — теперь pchaff к buffer уже не имеет никакого отношения).

А вот в таком виде твоя программа будет работать уже «странно»:

  // операция new с размещением

#include <iostream>
#include <new>                  // new с размещением
#include <cstring>

const int STRUCTSIZE = 2;       // размер массива структур
const int BUFFSIZE = 1;         // размер массива buffer
char buffer[BUFFSIZE];          // блок памяти под буфер
char test[50];

struct chaff{                   // определение структуры
    char dross[20];
    int slag;
};

int main()
{
    for (int t = 0; t < 50; t++)
        test[t] = 'a';
    test[49] = '\0';

    chaff matrix[STRUCTSIZE] = {        // инициализация массива структур
        {"Blablabla", 777},
        {"Lalala", 555}
    };

    std::cout << "Size of buffer: " << sizeof buffer << " bytes\n"; // размер буфера
    std::cout << "Size of matrix: " << sizeof matrix << " bytes\n"; // размер массива структур

    chaff * pchaff = new (buffer) chaff[STRUCTSIZE];        // выделение памяти с размещением
                                                        // в массиве buffer
    strcpy(pchaff[0].dross, "qwertyuiop");
    pchaff[0].slag = -1;
    strcpy(pchaff[1].dross, "asdfghjkl;");
    pchaff[1].slag = -2;

    std::cout << test << std::endl;

    pchaff = matrix;                // присвоение указателю адреса массива структур matrix

    for (int i = 0; i < STRUCTSIZE; i++){       // отображение массива структур
        std::cout << pchaff[i].dross << "\n"
            << pchaff[i].slag << "\n";
    }

    return 0;
}

поскольку при присваивании значений полям структуры ты будешь изменять значение переменной test.

Череп, вот вывод твоего варианта программы у меня в системе

Size of buffer: 1 bytes
Size of matrix: 48 bytes
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Blablabla
777
Lalala
555

Значения переменной тест не изменились. ???

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

#include <iostream>
#include <new>                  // new с размещением
#include <cstring>

const int STRUCTSIZE = 2;       // размер массива структур
const int BUFFSIZE = 1;         // размер массива buffer
char buffer[BUFFSIZE];          // блок памяти под буфер

struct chaff{                   // определение структуры
    char dross[20];
    int slag;
};

int main()
{
    chaff * pchaff = new (buffer) chaff[STRUCTSIZE];        // выделение памяти с размещением
                                                            // в массиве buffer

    strcpy(pchaff[0].dross, "Blablabla");   // инициализация массива структур
    strcpy(pchaff[1].dross, "Lalala");      
    pchaff[0].slag = 555;
    pchaff[1].slag = 777;

    std::cout << "Address of buffer: " << &buffer
        << "\nSize of buffer: " << sizeof buffer << "\n";
    std::cout << "Address of pchaff: " << pchaff
        << "\nSize of pchaff: " << sizeof pchaff << "\n";
    std::cout << "Size of *pchaff: " << sizeof *pchaff << "\n";

    for (int i = 0; i < STRUCTSIZE; i++){           // отображение массива структур
        std::cout << pchaff[i].dross << "\n"
            << pchaff[i].slag << "\n";
    }

    return 0;
}

И получил такой результат:

Address of buffer: 00AC9138
Size of buffer: 1
Address of pchaff: 00AC9138
Size of pchaff: 4
Size of *pchaff: 24
Blablabla
555
Lalala
777

То есть, значение на которое указывает указатель pchaff находиться по адресу по которому находиться массив buffer, но размер буфера == 1, размер указателя == 4, а размер значения на который указывает указатель (в данном случае первый элемент массива структур) == 24.
Вопрос: все-таки как это уместилось в одном байте??? В чем проблема? :)))

PS Кстати, копирование строковых литералов, в массив char dross[20] — это единственный способ инициализации массива в данной ситуации?

А если перед cout << test добавить строку

    std::cout << std::hex << "&buffer[0] = " << (unsigned)&buffer[0] << std::endl << "&test[0] = " << (unsigned)&test[0] << std::dec << std::endl;

То какой вывод будет?

Вот такой:

&buffer[0] = 106916a
&test[0] = 1069138

адреса разные.

Ничего не пойму. :(((
В первом коде я дал промашку. Но что же происходит во втором (переписанном) коде? Я же попал по адресу :)

Череп, объясняй, а то я уже начинаю думать, что операция new с размещением самая чудесная операция С++.
Я тут уже задумал S.T.A.L.K.E.Rа упаковать до 20 байт :)))

Получается вот что.

const int BUFFSIZE = 1;         // размер массива buffer
char buffer[BUFFSIZE];          // блок памяти под буфер

Здесь ты резервируешь буфер в 1 байт. На самом деле компилятор (видимо по соображениям выравнивания) выделяет 32 байта. Точнее говоря, выделяет 1 байт, а ещё 31 — «отходы производства» — не используются.

Если следом за этим дописываем

char test[50];

то получаем, что адрес начала buffer — 0x106916a, а адрес начала test — 0x1069138 (разность адресов — 32 байта).

Теперь ты делаешь

    chaff * pchaff = new (buffer) chaff[STRUCTSIZE];        // выделение памяти с размещением
                                                            // в массиве buffer

    strcpy(pchaff[0].dross, "Blablabla");   // инициализация массива структур
    strcpy(pchaff[1].dross, "Lalala");      
    pchaff[0].slag = 555;
    pchaff[1].slag = 777;

Операция new по адресу buffer создаёт первый объект типа chaff (вызывает конструктор структуры без аргументов, сгенерированный автоматически, который, естественно, ничего не делает), затем по адресу buffer+24 таким же образом создаёт второй объект типа chaff. Получается следующая картина:

Схема размещения переменных в памяти
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

buffer      buffer+32  buffer+48
              test     |                     test+50
|-|.............|------------------------------|
|----------||----------|
chaff[0]    chaff[1]
24 байта    24 байта

т.е. первый chaff «успешно» умещается в отведённые компилятором под buffer 32 байта, а второй chaff уже залезает на массив test. Что я тебе и пытался продемонстрировать.

Так что фокус с упаковкой в 20 байт не получится )) Ты просто работаешь с памятью, которая тебе не принадлежит.

Вот ещё одна вариация «на тему»

#include <iostream>
#include <new>                  // new с размещением
#include <cstring>

const int STRUCTSIZE = 2;       // размер массива структур
const int BUFFSIZE = 1;         // размер массива buffer
char buffer[BUFFSIZE];          // блок памяти под буфер

const int TESTSIZE = 75;
char test[TESTSIZE];

const int DROSSSIZE = 40;
struct chaff{                   // определение структуры
    char dross[DROSSSIZE];
    chaff();
};

chaff::chaff() {
    std::cout << "constructor chaff: address = " << std::hex << this << std::dec << std::endl;
    for (char i = 0; i < DROSSSIZE; i++)
        dross[i] = 'A' + i;
    dross[DROSSSIZE-1] = '\0';
}

int main()
{

    for (int t = 0; t < TESTSIZE; t++)
        test[t] = '+';
    test[TESTSIZE-1] = '\0';

    chaff * pchaff = new (buffer) chaff[STRUCTSIZE];        // выделение памяти с размещением
                                                            // в массиве buffer

    std::cout 
        << std::hex << "&buffer[0] = " << (void *)&buffer[0] << std::endl 
        << "&test[0] = " << (void *)&test[0] << std::dec << std::endl;

    for (int t = 0; t < TESTSIZE; t++)
        std::cout << (test[t] < ' ' ? '#' : test[t]); // непечатные символы выводим как '#"
    std::cout << std::endl;

    std::cout << "Address of buffer: " << &buffer
        << "\nSize of buffer: " << sizeof buffer << "\n";
    std::cout << "Address of pchaff: " << pchaff
        << "\nSize of pchaff: " << sizeof pchaff << "\n";
    std::cout << "Size of *pchaff: " << sizeof *pchaff << "\n";

    for (int i = 0; i < STRUCTSIZE; i++){           // отображение массива структур
        std::cout << "[" << i << "] "<< pchaff[i].dross << "\n";
    }

    std::cout << std::endl;

    for (int t = 0; t < TESTSIZE; t++)
        test[t] = '+';
    test[TESTSIZE-1] = '\0';

    for (int i = 0; i < STRUCTSIZE; i++){           // отображение массива структур
        std::cout << "[" << i << "] "<< pchaff[i].dross << "\n";
    }
    return 0;
}

У меня выдаёт

constructor chaff: address = 0x488020
constructor chaff: address = 0x488048
&buffer[0] = 0x488020
&test[0] = 0x488040
abcdefg#ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg#++++++++++++++++++++++++++#
Address of buffer: 0x488020
Size of buffer: 1
Address of pchaff: 0x488020
Size of pchaff: 4
Size of *pchaff: 40
[0] ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg
[1] ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg

[0] ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
[1] ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Обрати внимание на адреса, которые выводит конструктор, и адреса buffer и test. И сравни со схемкой размещения переменных в памяти (см. выше).

Если в этой программке DROSSSIZE установить в 2048, то у меня программа завершается аварийно.

Компилятор: TDM-GCC 4.8.1 32-bit Debug

Спасибо, Череп, за комментарии.
В общем я «разобрал» свой компилятор почти на части. Добрался до функций и переменных которыми оперирует компилятор при выделении памяти (ОЧЕНЬ МНОГО ВСЕГО НЕ ПОНЯТНОГО, но обо всем по порядку).

Учитывая твои комментарии пришел к выводу, что при использовании операции new с размещением контроль за переполнением буфера полностью лежит на программисте.
Исходя из этого, решил написать все, как говорится, «красиво». Под создание массива из двух структур размером 48 байт, выделил под буфер 50 байт (кстати, компилятор внутренне действительно выделяет памяти больше чем запрашивается, в данном случае 86 байт) и получилось, на мой взгляд, все просто, лаконично и понятно. Выделил, разместил и ...

#include <iostream>
#include <new>                  // new с размещением
#include <cstring>

const int STRUCTSIZE = 2;       // размер массива структур
const int BUFFSIZE = 50;        // размер массива buffer
char buffer[BUFFSIZE];          // блок памяти под буфер

struct chaff{                   // определение структуры
    char dross[20];
    int slag;
};

int main()
{
    chaff * pchaff = new (buffer) chaff[STRUCTSIZE];    // выделение памяти с размещением
                                                        // в массиве buffer

    strcpy(pchaff[0].dross, "Blablabla");   // инициализация массива структур
    strcpy(pchaff[1].dross, "Lalala");      
    pchaff[0].slag = 555;
    pchaff[1].slag = 777;

    std::cout << "Address of buffer: " <<  &buffer
        << "\nSize of buffer: " << sizeof buffer << "\n";
    std::cout << "Address of &pchaff[0]: " << &pchaff[0]
        << "\nSize of pchaff: " << sizeof pchaff << "\n";
    std::cout << "Size of pchaff[0]: " << sizeof pchaff[0] << "\n";
    std::cout << "Address of &pchaff[1]: " << &pchaff[1] << "\n";

    for (int i = 0; i < STRUCTSIZE; i++){           // отображение массива структур
        std::cout << pchaff[i].dross << "\n"
            << pchaff[i].slag << "\n";
    }

    return 0;
}

и получил такой вывод, который меня снова поставил в тупик.

 Address of buffer: 0108A138
 Size of buffer: 50 bytes
 Address of &pchaff[0]: 0108A138
 Size of pchaff: 4 bytes
 Size of pchaff[0]: 24 bytes
 Address of &pchaff[1]: 0108A150
 Blablabla
 555
 Lalala
 777

Адрес и размер структуры pchaff[0],

Address of &pchaff[0]: 0108A138
Size of pchaff[0]: 24 bytes

а адрес структуры pchaff[1]

Address of &pchaff[1]: 0108A150

на 18 байт больше.
Вопрос 1: Что за магия? Что я упускаю и не могу понять?

Решил продублировать себя с помощью динамически выделенного массива под буфер (из соображения, что компилятор пресечет все попытки чтения памяти за пределами массива buffer). Мои надежды оправдались, компилятор действительно стал «ругаться» если я устанавливал значение BUFFSIZE меньше чем внутренне выделяется компилятором.
Но написав такой код, снова натыкаюсь на те же грабли, что и в первом примере.

#include <iostream>
#include <new>                  // new с размещением
#include <cstring>

const int STRUCTSIZE = 2;       // размер массива структур
const int BUFFSIZE = 50;            // размер массива buffer
char buffer[BUFFSIZE];          // блок памяти под буфер

struct chaff{                   // определение структуры
    char dross[20];
    int slag;
};

int main()
{

    char * buffer2 = new char[BUFFSIZE];                    // выделение динамической памяти под буфер
    chaff * pchaff2 = new (buffer2) chaff[STRUCTSIZE];  // new с размещением в буфере buffer2

    strcpy(pchaff2[0].dross, "chachachacha");
    strcpy(pchaff2[1].dross, "dadada");
    pchaff2[0].slag = 333;
    pchaff2[1].slag = 999;

    std::cout << "Address of buffer2: " <<  (void *)buffer2
        << "\nSize of buffer2: " << sizeof buffer2 << "\n";
    std::cout << "Address of pchaff2[0]: " << &pchaff2[0]
        << "\nSize of pchaff2: " << sizeof pchaff2 << "\n";
    std::cout << "Size of *pchaff2[0]: " << sizeof pchaff2[0] << "\n";
    std::cout << "Address of pchaff2[1]: " << &pchaff2[1] << "\n";

    for (int i = 0; i < STRUCTSIZE; i++){           // отображение массива структур
        std::cout << pchaff2[i].dross << "\n"
            << pchaff2[i].slag << "\n";
    }


    delete [] buffer2;  // освободить память выделеную динамически под буфер

    return 0;
}

Вывод такой

Address of buffer2: 002B7708
Size of buffer2: 4
Address of pchaff2[0]: 002B7708
Size of pchaff2: 4
Size of *pchaff2[0]: 24
Address of pchaff2[1]: 002B7720
chachachacha
333
dadada
999

Снова те же грабли только в профиль.
Вопрос 2: См. вопрос 1 :))) (What's hell of this? Sorry.)
Череп, объясни для тех кто на бронепоезде ))) За ранее спасибо.

Ответ 1. Отдыхать вам, батенька, больше надо ))
И считать в единой системе счисления. 0x0108A150 - 0x0108A138 = 0x18, что в десятичной системе равно 24 байтам.

Ответ 2. См. ответ 1 ))

Я, правда, не понял кто и на что у тебя стал ругаться. Компилятор? Ему вроде должно быть фиолетово. Или ты «получал» при выполнении программы? Т.е. от исполняющей системы (RTS — run-time system, она же CRT — C run-time) C++?

Хочется все ,вся и сразу охватить своим «умишкой», а на самом простом — грабли :((( Семен Семеныч!!! Эта операция new с размещением мне чуть «крышу» не сорвала :)))
Спасибо, Череп!!!

1. Кстати, я тут между делом, вопрос задавал

Копирование строковых литералов, в массив char dross[20] — это единственный способ инициализации массива в данной ситуации?

Потому как компилятор «ругается» (выдает предупреждение)

warning C4996: 'strcpy': This function or variable may be unsafe. Consider using strcpy_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
1> c:\program files (x86)\microsoft visual studio 10.0\vc\include\string.h(105) : see declaration of 'strcpy'

Можно ли в таких ситуациях использовать в программе такое определение, как предлагает компилятор

#define  _CRT_SECURE_NO_WARNINGS
#pragma warning(disable : 4996)

дабы отключить вывод предупреждения?

2. И еще, я где-то тебя спрашивал, не найду, можно ли на одной ОС ставить два компилятора?
Ты говорил, что можно, только нужно соблюдать осторожность.
У меня установлена MS VS 2010. Если можно установить второй компилятор, то какой: Code::Blocks или DevC++? И какие осторожности нужно соблюдать?
Честно говоря я пытался поставить Dev так у меня все значки в проектах VS с расширением .h и .cpp стали отображаться как значки Dev. И пытался поставить Code::Blocks, при установке всплывает вопрос — Сделать компилятор TDM-GCC по умолчанию? Что это значит и какие после этого будут последствия хз?

Подскажи пожалуйста.

ЗАРАНЕЕ СПАСИБО!!! )))

Отдыхать вам, батенька, больше надо ))

Это, наверное, один из главных постулатов программирования!!!)))
Раз, два, три, четыре, пять начинаю отдыхать... ... ...

  1. Кстати, я тут между делом, вопрос задавал

Копирование строковых литералов, в массив char dross[20] — это единственный способ инициализации массива в данной ситуации?

Массив типа char — он и есть обычный массив типа char. А какой-то особой «ситуации» я не вижу. Некий альтернативный способ я применил в примере в моём посте ранее.

Потому как компилятор «ругается» (выдает предупреждение)

warning C4996: 'strcpy': This function or variable may be unsafe. Consider using strcpy_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
1> c:\program files (x86)\microsoft visual studio 10.0\vc\include\string.h(105) : see declaration of 'strcpy'

Хм... А ты понял, что тебе написал компилятор?

Вообще там написано, что функция strcpy небезопасна. И рекомендуют вместо неё использовать функцию strcpy_s. Далее «бла-бла» про то, как подавить вывод предупреждения.

К твоим изысканиям по поводу new это сообщение не имеет никакого отношения.

Я бы тебе рекомендовал использовать strcpy_s, а не давить предупреждения. (Вообще, давить предупреждения — плохая практика. Можно не заметить грабли гигантского размера.)

Однако с safe-версиями некоторых функций есть одна неприятность: они не стандартизованы, т.е. в другом компиляторе их может не быть (например в MinGW gcc). Код становится непереносимым, если не предпринять некоторых мер. Но если ты однозначно работаешь с MS VC++, то на счёт переносимости можешь не заморачиваться.

И еще, я где-то тебя спрашивал, не найду, можно ли на одной ОС ставить два компилятора?

На самом деле ты спрашиваешь «можно ли ставить несколько IDE?». Компилятор, обычно, это просто приложение с интерфейсом командной строки, которое не «встраивается» в систему. А вот IDE (интегрированная среда разработки) может перехватывать на себя, например, ассоциацию файлов (по расширению имён файлов) или встраивать в ОС свой отладчик (возможно с установкой драйверов, служб и пр.).

С расширениями имён файлов обычно всё достаточно просто. Можно всегда перенастроить либо средствами самой IDE, либо через возможности ОС. Расширение имени файла для файла проекта конкретной IDE обычно уникально и не требует перенастройки.

С интеграцией отладчиков в систему надо быть осторожнее. Иногда (редко) бывают конфликты. Да и лишние активные драйверы/службы в системе не нужны. Надо пробовать. Если что-то пойдёт не так — переустановить одну или обе IDE. При установке инсталлятор обычно задаёт вопрос об установке отладчика.

Конкретно по MinGW и MS VS: уживаются совершенно мирно. MinGW не интегрирует свой отладчик в систему.

Code::Blocks умеет использовать множество компиляторов, в частности и MinGW gcc, и MS VC++. Можно установить только IDE без компилятора (в комплекте может быть MinGW — смотри какой дистрибутив скачиваешь).

У меня, например, установлены MS VS, DevC++ и Code::Blocks. Причём последние две IDE используют один и тот же компилятор MinGW, который шёл в комплекте с DevC++. Сначала был поставлен MS VS, потом добавлен DevC++ с MinGW, потом поставил Code::Blocks без компилятора. Полёт нормальный.

По удобству пользования, IMHO, Code::Blocks получше, чем DevC++. Хотя и у DevC++ есть свои прелести. Но MS VS по удобству — сильно впереди, безо всяких IMHO, и по редактированию исходников, и по отладке.

Честно говоря я пытался поставить Dev так у меня все значки в проектах VS с расширением .h и .cpp стали отображаться как значки Dev.

Перенастроить ассоциацию файлов .h и .cpp на VS можно в VS: меню Сервис -> Параметры -> Среда -> Общие -> кнопка Управление сопоставлением расширений файлов.

И пытался поставить Code::Blocks, при установке всплывает вопрос — Сделать компилятор TDM-GCC по умолчанию? Что это значит и какие после этого будут последствия хз?

Это оно для себя делает компилятор по умолчанию. Во вне IDE эта установка не распространяется.

Внимание! Это довольно старый топик, посты в него не попадут в новые, и их никто не увидит. Пишите пост, если хотите просто дополнить топик, а чтобы задать новый вопрос — начните новый.

Ответить

Вы можете использовать разметку markdown для оформления комментариев и постов. Используйте функцию предпросмотра для проверки корректности разметки.

Пожалуйста, оформляйте исходный код в соответствии с правилами разметки. Для того, чтобы вставить код в комментарий, скопируйте его в текстовое поле ниже, после чего выделите то, что скопировали и нажмите кнопку «код» в панели инструментов. Иначе ваш код может принять нечитаемый вид.

Либо производите оформление кода вручную, следующим образом:

``` #include <iostream> using namespace std; int main() { // ... } ```

Предпросмотр сообщения

Ваше сообщение пусто.