Версия для печати темы

Нажмите сюда для просмотра этой темы в обычном формате

Форум на CrossPlatform.RU _ С\С++ _ копирование и память

Автор: rp80 22.12.2011, 13:47

Копирование указателя на объект не приводит к копированию объекта.
Значит следующий код приведет к утечки памяти:

    const char* c =new char[30];
    const char* cc="zca";
    c=cc;//потеряны 30 байт на которые указывал с


Правильно копировать так:
    char* c =new char[30];
    char* cc="zca";
    delete[] c;
    c=new char[sizeof(cc)];
    for(size_t i=0;i<sizeof(cc);++i)
    c[i]=cc[i];


А что происходит при копировании переменных в стеке?
const char* s="xzczv";
const char* s1="vvb";
s=s1;//s и s1 указывают на одно и тоже место в памяти

Теряется ли память выделенная первоначально под s?

Автор: Алексей1153 22.12.2011, 13:52

Цитата(rp80 @ 22.12.2011, 16:47) *
Правильно копировать так:
    char* c =new char[30];
    char* cc="zca";
    delete[] c;
    c=new char[sizeof(cc)];
    for(size_t i=0;i<sizeof(cc);++i)
    c[i]=cc[i];


лучше уж так

    char* c =new char[30];
    char* cc="zca";

    delete[] c;
    c=сс;



Цитата(rp80 @ 22.12.2011, 16:47) *
А что происходит при копировании переменных в стеке?
const char* s="xzczv";
const char* s1="vvb";
s=s1;//s и s1 указывают на одно и тоже место в памяти


здесь массивы заданы статически , они являются константами. Причём на стеке - только указатели, сами данные в странице данных
Ничего никуда не утечёт

Автор: BRE 22.12.2011, 13:53

Цитата(rp80 @ 22.12.2011, 14:47) *
А что происходит при копировании переменных в стеке?
Теряется ли память выделенная первоначально под s?

Нет.

Автор: Iron Bug 22.12.2011, 14:03

Цитата(rp80 @ 22.12.2011, 16:47) *
А что происходит при копировании переменных в стеке?

после выхода из функции стек будет почищен от временных переменных. так что ничего не теряется. но и смысла особого в этом нет.

Автор: rp80 22.12.2011, 14:06

Цитата(Алексей1153 @ 22.12.2011, 14:52) *
лучше уж так
    char* c =new char[30];
    char* cc="zca";

    delete[] c;
    c=сс;

Ага, можно и так.
Цитата(Алексей1153 @ 22.12.2011, 14:52) *
здесь массивы заданы статически , они являются константами. Причём на стеке - только указатели, сами данные в странице данных
Ничего никуда не утечёт

А можно поподробнее что за страница данных,какая разница константы они или нет? Разве если просто char* без const написать что-то изменится в работе с памятью?
Потом даже если в стеке только указатели, но сами-то данные должны тоже где-то хранится, соответственно под них выделена где-то память, что с этой памятью происходит в момент копирования?

Ну или где почитать про это. Не получается ничего толкового нагуглить

Цитата(Iron Bug @ 22.12.2011, 15:03) *
Цитата(rp80 @ 22.12.2011, 16:47) *
А что происходит при копировании переменных в стеке?

после выхода из функции стек будет почищен от временных переменных. так что ничего не теряется. но и смысла особого в этом нет.

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

Автор: Iron Bug 22.12.2011, 14:47

Цитата(rp80 @ 22.12.2011, 17:06) *
Понятно, что при выходе почистится стек, но если в блоке (функции) мы создадим массив на 2гб и он при таком копировании потеряется, то на следующий уже может не хватить места.

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

Автор: rp80 22.12.2011, 14:54

Цитата(Iron Bug @ 22.12.2011, 15:47) *
ну, какбэ стек не резиновый. и два гигабайта под стек, может, и можно выделить (опцией при компиляции), но нецелесообразно. если собираешься работать с большими массивами - это куча и динамическое размещение.

Да это понятно. Вообще работа со стеком крайне быстра при объеме стека меньше 4кб, а далее все становится медленнее. И советуют в общем случае если функция работает с памятью больше 1 мб, то по-любому выделять память в куче.
Но вопрос не в этом ) А в том, что хочется понять что конкретно происходит с памятью при копировании локальных переменных.

Автор: Iron Bug 22.12.2011, 15:01

Цитата(rp80 @ 22.12.2011, 17:54) *
Но вопрос не в этом ) А в том, что хочется понять что конкретно происходит с памятью при копировании локальных переменных.

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

Автор: BRE 22.12.2011, 15:07

Цитата(rp80 @ 22.12.2011, 15:54) *
что конкретно происходит с памятью при копировании локальных переменных.

Все тоже самое, что и при копировании любых других переменных. :)
Стек - это таже самая память, отличия проявляются в механизме выделения/освобождения.

Автор: rp80 22.12.2011, 15:47

Цитата(Iron Bug @ 22.12.2011, 16:01) *
при копировании переменных данные копируются. а тут ты просто манипулируешь указателями на область данных (ибо константы), так что копируются только значения самих указателей.

Ну вот так вроде понятнее стало. Но все же, чтобы до конца понять ответьте пожалуйста по пунктам. Допустим размер стека 0 байт.
{
char* s="aaa";//В стеке создается переменная типа char*. Размер стека теперь 4 байта.
//Сами данные пишутся в другое место ?

В какое?


  {
    char* s2="zxcz";//То же самое, что и выше.Размер стека 8 байт
    s=s2;//s указывает туда же куда и s2.

Что происходит с данными s? Как освобождается память занимаемая ими? И освобождается ли вообще?


  }// Здесь переменная s2 уничтожается  Размер стека 4 байта

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


    std::cout<<s<<std::endl;//Выводит zxcz
};//Здесь уничтожается s

Но уничтожаются ли тут данные, на которые указывала s? Ведь выше в аналогичном случае этого не произошло.


Спасибо.

Автор: Iron Bug 22.12.2011, 16:03

Цитата(rp80 @ 22.12.2011, 15:47) *
В какое?

в сегмент данных. впрочем, может, компилятор для оптимизации их даже в сегмент кода упихивает, ибо константы не меняются, а ближние указатели быстрее.
Цитата(rp80 @ 22.12.2011, 15:47) *
Что происходит с данными s? Как освобождается память занимаемая ими? И освобождается ли вообще?

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


Автор: ilyabvt 22.12.2011, 17:50

Цитата(rp80 @ 22.12.2011, 13:47) *
Правильно копировать так:
    char* c =new char[30];
    char* cc="zca";
    delete[] c;
    c=new char[sizeof(cc)];
    for(size_t i=0;i<sizeof(cc);++i)
    c[i]=cc[i];

Не правильно. sizeof(cc) вернет размер указателя, а не массива. В данном случае это сработает только по тому что вы создали 4-х байтный массив.

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

Теоретически возможно. Только это путь даже не джедая, а какого-то безумного ситха, ибо придется не только найти строки в памяти и удалить, но и подредактировать код (не исходный, а тот что висит в памяти в виде процессорных иструкций) так чтобы прога думала что никакой переменной и не существовало. :D

Автор: rp80 22.12.2011, 19:17

Цитата(Iron Bug @ 22.12.2011, 17:03) *
с константными строками ничего не происходит. они остаются. компилятор всё равно выделяет под них место в данных - нет смысла их удалять. во всяком случае, мне не известно о какой-либо хитрой рантайм оптимизации неиспользованных данных. а если это оптимизированное хранение констант в сегменте кода, то их в принципе невозможно удалить. если хочешь, чтобы строки не занимали место - грузи их динамически из какого-то стороннего файла.


Т.е. если где-то в функции мы создаем десять строк по 100 мб, то экзешник будет 1000+ мб?
Ну и правильно я понимаю, что строки это не исключение из правил? Значит значения любых базовых или пользовательских локальных переменных и массивов этих переменных сохраняются в коде?

Автор: ilyabvt 22.12.2011, 19:50

Цитата(rp80 @ 22.12.2011, 22:17) *
Цитата(Iron Bug @ 22.12.2011, 17:03) *
с константными строками ничего не происходит. они остаются. компилятор всё равно выделяет под них место в данных - нет смысла их удалять. во всяком случае, мне не известно о какой-либо хитрой рантайм оптимизации неиспользованных данных. а если это оптимизированное хранение констант в сегменте кода, то их в принципе невозможно удалить. если хочешь, чтобы строки не занимали место - грузи их динамически из какого-то стороннего файла.


Т.е. если где-то в функции мы создаем десять строк по 100 мб, то экзешник будет 1000+ мб?
Ну и правильно я понимаю, что строки это не исключение из правил? Значит значения любых базовых или пользовательских локальных переменных и массивов этих переменных сохраняются в коде?


Не забывайте что речь идет о константных строках, т.е. тех значение которых мы явно задаем в программе. Вы часто в исходнике пишете строку длинною 100 миллионов символов? Строки действительно не исключение, числа заданные явно тоже хранятся в коде.
Вот например есть код:
int a = 2;

Переменная 'а' хранится в стеке, а число 2 в коде.

Автор: rp80 22.12.2011, 20:03

Цитата(ilyabvt @ 22.12.2011, 20:50) *
Не забывайте что речь идет о константных строках, т.е. тех значение которых мы явно задаем в программе. Вы часто в исходнике пишете строку длинною 100 миллионов символов? Строки действительно не исключение, числа заданные явно тоже хранятся в коде.
Вот например есть код:
int a = 2;

Переменная 'а' хранится в стеке, а число 2 в коде.

Спасибо за пояснение. Ну есстественно, программист сам не задает такие строки. Но, например, строку в которую читают из файла размером в 100 000 000 символов представить вполне можно. Это же тоже явное задание?

Автор: ilyabvt 22.12.2011, 20:13

Цитата(rp80 @ 22.12.2011, 23:03) *
Цитата(ilyabvt @ 22.12.2011, 20:50) *
Не забывайте что речь идет о константных строках, т.е. тех значение которых мы явно задаем в программе. Вы часто в исходнике пишете строку длинною 100 миллионов символов? Строки действительно не исключение, числа заданные явно тоже хранятся в коде.
Вот например есть код:
int a = 2;

Переменная 'а' хранится в стеке, а число 2 в коде.

Спасибо за пояснение. Ну есстественно, программист сам не задает такие строки. Но, например, строку в которую читают из файла размером в 100 000 000 символов представить вполне можно. Это же тоже явное задание?

Нет конечно, мало ли что будет в этом файле (и будет ли он вообще) на момент компиляции. А потом, файл вы считываете в переменную. А речь сейчас не о них. Речь о константных данных. Явное задание это то что программист указывает в коде.

Автор: rp80 22.12.2011, 20:31

Цитата(ilyabvt @ 22.12.2011, 21:13) *
Нет конечно, мало ли что будет в этом файле (и будет ли он вообще) на момент компиляции. А потом, файл вы считываете в переменную. А речь сейчас не о них. Речь о константных данных. Явное задание это то что программист указывает в коде.

Хорошо, а где хранятся данные переменных? В самом стеке? И в примере с чтением из файла в какой-то момент будет переполнение стека, если файл достаточно большой?

Автор: Iron Bug 22.12.2011, 20:35

Цитата(ilyabvt @ 22.12.2011, 20:50) *
Теоретически возможно. Только это путь даже не джедая, а какого-то безумного ситха, ибо придется не только найти строки в памяти и удалить, но и подредактировать код (не исходный, а тот что висит в памяти в виде процессорных иструкций) так чтобы прога думала что никакой переменной и не существовало. :D

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

Цитата(rp80 @ 22.12.2011, 23:31) *
Хорошо, а где хранятся данные переменных? В самом стеке? И в примере с чтением из файла в какой-то момент будет переполнение стека, если файл достаточно большой?

смотря каких. константы могут быть сложены в сегмент кода (раз они не меняются). переменные, которые обычные - в сегменте данных. можно ещё регистровые переменные создавать - они, соответственно, будут размещены в регистрах, если компилятор сможет их так распределить. если не сможет - то всё равно в сегменте данных.

Автор: rp80 22.12.2011, 23:11

Цитата(Iron Bug @ 22.12.2011, 21:35) *
смотря каких. константы могут быть сложены в сегмент кода (раз они не меняются). переменные, которые обычные - в сегменте данных. можно ещё регистровые переменные создавать - они, соответственно, будут размещены в регистрах, если компилятор сможет их так распределить. если не сможет - то всё равно в сегменте данных.

Вообще вот цитата из википедии: A data segment is a portion of virtual address space of a program, which contains the global variables and static variables that are initialized by the programmer.
Т.е. они называют сегментом данных то что вы называете сегментом кода. Непонятно кто прав..
А насчет утечек вот пример
int main()
{
    int* pi;
    {
        int ar[1000000];
        ar[3]=std::rand() % 10 + 1;
        pi=ar;
    }
    std::cout<<pi[3];
}

Работает, не падает, разве это не говорит об утечке? Блок закрылся, переменная ar удалена из стека, но выделенный массив остался в памяти.

И если я меняю размер массива на 10, то размер экзешника уменьшается (18693 на 18677). Т.е. похоже на то, что данные хранятся в коде. Но тогда получается что такой неявно заданный объект тоже хранит данные в сегменте кода.

И везде где я читаю пишут что есть 3 типа памяти: Сегмент кода - хранит текст программы + константы и статик переменные. Стэк - хранит локальные переменные. Куча - хранит динамические переменные. Нигде не видел упоминание о сегменте данных как о 4ом виде памяти, связанном со стеком.

Автор: Алексей1153 23.12.2011, 7:33

rp80, стек то никуда не девается. А то, что ты правильно выводишь якобы - это уже мусор, одна ко по чистой случайности этот мусор пока ещё соответствует тому, что в него поместили внутри вложенного блока

попробуй вот так

int main()
{
    int* pi;
    {
        int ar[1000000];
        ar[3]=std::rand() % 10 + 1;
        pi=ar;
    }
  
    int ar2[20]={1,2,3,4};
    std::cout<<pi[3];
}

Автор: Iron Bug 23.12.2011, 7:41

Цитата(rp80 @ 23.12.2011, 2:11) *
Вообще вот цитата из википедии: A data segment is a portion of virtual address space of a program, which contains the global variables and static variables that are initialized by the programmer.Т.е. они называют сегментом данных то что вы называете сегментом кода. Непонятно кто прав..

ты дальше-то читай википедию. там в целом правильно написано. кусок константных данных в коде они там называют Rodata. а перезаписываемые данные, соответственно, сегментом данных. реализации распределения некоторых переменных в языках высокого уровня могут отличаться. какие-то компиляторы оптимизируют константы и помещают их в код, какие-то - нет, и оставляют их в сегменте данных. в любом случае есть основная разница: сегмент кода не доступен для записи, сегмент данных и стек - это всё, что меняется.
если хочешь глубже понять принципы - читай про ассемблер.
если тебя интересует конкретная реализация - ты можешь просто посмотреть адреса в отладчике, и сравнить с регистрами. в архитектуре PC сегмент кода - это то, куда указывает CS. стек - SS. а данные, как правило, помещаются либо в DS, либо в ES (но там могут быть и адреса других сегментов, если идёт обработка константных данных, например). однако, в дебажном варианте распределение немного другое, там есть ловушки для нулевых смещений и выхода за границы массивов, отсутствие оптимизации размещения и всяческие компиляторные вставки для отладки. там может быть немного другое распределение.

Автор: BRE 23.12.2011, 9:17

Цитата(Iron Bug @ 23.12.2011, 8:41) *
если тебя интересует конкретная реализация - ты можешь просто посмотреть адреса в отладчике, и сравнить с регистрами. в архитектуре PC сегмент кода - это то, куда указывает CS. стек - SS. а данные, как правило, помещаются либо в DS, либо в ES (но там могут быть и адреса других сегментов, если идёт обработка константных данных, например). однако, в дебажном варианте распределение немного другое, там есть ловушки для нулевых смещений и выхода за границы массивов, отсутствие оптимизации размещения и всяческие компиляторные вставки для отладки. там может быть немного другое распределение.

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

Автор: rp80 23.12.2011, 9:46

Цитата(Алексей1153 @ 23.12.2011, 8:33) *
rp80, стек то никуда не девается. А то, что ты правильно выводишь якобы - это уже мусор, одна ко по чистой случайности этот мусор пока ещё соответствует тому, что в него поместили внутри вложенного блока

попробуй вот так

int main()
{
    int* pi;
    {
        int ar[1000000];
        ar[3]=std::rand() % 10 + 1;
        pi=ar;
    }
  
    int ar2[20]={1,2,3,4};
    std::cout<<pi[3];
}


Ничего не меняется, какие переменные потом не определяй, все равно правильно работает )

Автор: Алексей1153 23.12.2011, 9:49

rp80, значит компилятор разместил переменную не так, как я подумал ) Но это неважно, суть ты понял, я полагаю

Автор: Iron Bug 23.12.2011, 9:55

Цитата(BRE @ 23.12.2011, 12:17) *
(в сегментных регистрах храняться уже не адреса, а дескрипторы сегментов из специальных дескрипторных таблицах GDT или LDT

а, ну да. тут ещё оттуда придётся тащить данные. у дров есть функция взятия физического адреса, а вот в юзерспейсе я чота не помню, есть такая фича или нет... я-то живу в основном в кернеле :) а для 64 бит GDT чистая абстракция - там один хрен всё линейно.
в общем, тогда лучше не пытаться гадать, а взять какой-нить стандарт ELF или вендозные спецификации и почитать про конкретные оси и конкретные компиляторы. впрочем, от этого разницы никакой не будет. для программиста интерес представляет размер стека, оптимизация работы с указателями и решение использования стека или кучи. я в юзерских программах не парюсь и использую кучу. ибо она практически не ограничена и чаще всего скорость доступа совершенно не критична, так как юзерские программы обычно и без того тормозят из-за вывода и экономия на адресах не даёт никакого эффекта.

Автор: BRE 23.12.2011, 10:23

Цитата(Iron Bug @ 23.12.2011, 10:55) *
а, ну да. тут ещё оттуда придётся тащить данные. у дров есть функция взятия физического адреса, а вот в юзерспейсе я чота не помню, есть такая фича или нет...

Userspace вообще ничего не знает про физические адреса - он живет в виртуальном адресном пространстве.

Автор: rp80 23.12.2011, 10:39

Цитата(Iron Bug @ 23.12.2011, 10:55) *
я в юзерских программах не парюсь и использую кучу. ибо она практически не ограничена и чаще всего скорость доступа совершенно не критична, так как юзерские программы обычно и без того тормозят из-за вывода и экономия на адресах не даёт никакого эффекта.


Я бы тоже не стал лазить в такие дебри. Но стал тут разбираться с копированием объектов и вот такое поведение непонятно.
class A
{
    public:
    int ar[3];
    int* pi;
    char* str;
    A(){ar[0]=ar[1]=ar[2]=0;pi=new int[3];}
};

int main()
{
    A a1,a2;
    a2=a1;
    a1.ar[0]=98;
    a1.pi[0]=123;
    a1.str="zxcf";
    std::cout<<a2.ar[0]<<"\n"<<a2.pi[0]<<"\n"<<a2.str<<std::endl;
}

Выводит: 0 - 123 - Segmentation fault.
Т.е. скопировался как надо только указатель на динамически выделенную память.

Автор: Алексей1153 23.12.2011, 10:41

rp80, как разберёшься с указателями на массивы, открой для себя std-контейнеры )

а в данном случае тебе нужно переопределить оператор присваивания (и конструктор по умолчанию) для корректного копирования

Автор: rp80 23.12.2011, 10:47

Цитата(Алексей1153 @ 23.12.2011, 11:41) *
rp80, как разберёшься с указателями на массивы, открой для себя std-контейнеры )

а в данном случае тебе нужно переопределить оператор присваивания (и конструктор по умолчанию) для корректного копирования

Так в том то и дело, что по идее копирование по умолчанию должно копировать почленно. И если мы потом поменяем значение по одному указателю, должно поменяться и второе

Автор: BRE 23.12.2011, 10:56

Цитата(rp80 @ 23.12.2011, 11:39) *
Т.е. скопировался как надо только указатель на динамически выделенную память.

Кому надо? Компилятор сделал то что ты ему сказал.
Сделал бинарную копию a2 равную a1 (потеряв при этом указатель на область памяти pi - утечка).
Указатель a2.str как показывал "в никуда", так и показывает - отсюда вылет программы.
Все ожидаемо.


Цитата(rp80 @ 23.12.2011, 11:47) *
И если мы потом поменяем значение по одному указателю, должно поменяться и второе

Так и есть. Там где ты меняешь что-то по указателю, там и меняется (a1.pi[ 0 ] и a2.pi[ 0 ] ).

Автор: Алексей1153 23.12.2011, 14:36

rp80,


class A
{
    public:
    int ar[3];
    std::vector<int> pi;
    std::string str;
};

A a1,a2;

a1=a2 //корректно

Форум Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)