C++ управление памятью ссылочных типов

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

Прежде всего, мое понимание ссылочных типов:

Указатель помещается в стек, а фактические данные, на которые указывает указатель, создаются и помещаются в кучу. Стандартные массивы и определяемые пользователем классы являются ссылочными типами. Это верно? Во-вторых, мой главный вопрос: всегда ли механизмы управления памятью c и c++ (malloc, free and new, delete) обрабатывают это правильно и освобождают память, на которую указывает класс или массив? Все ли по-прежнему работает, если эти указатели каким-то образом переназначаются другим объектам того же размера/типа в куче? Что, если в классе есть член-указатель, указывающий на другой объект? Я предполагаю, что удаление/освобождение объекта класса не освобождает то, на что указывает указатель члена, это правильно?

Спасибо всем!

-R


person Russel    schedule 18.01.2011    source источник
comment
Вы путаете C++ с Java. Получите подходящую книгу по C++ и учитесь с самого начала. Ваше знание Java не применимо. stackoverflow.com/questions/388242 /   -  person Benjamin Lindley    schedule 18.01.2011
comment
Спасибо за ответы всем. Я знаю, что мои случайные метания слов malloc, free, new и delete подразумевают, что я их путаю, но я знаю разницу. Я просто пытаюсь выучить C и C++ одновременно и просто закинул туда все. Кроме того, для тех людей, которые думают, что я смешиваю вещи Java, они близки. Это С#, с которым я путаюсь :)   -  person Russel    schedule 18.01.2011
comment
Думаю, меня больше всего смущало то, как c/c++ создает объекты. Они не являются ссылочными типами в смысле С#, а скорее объектами, которые помещаются в стек, если только они не были динамически выделены, скажем, «новыми», верно? Как насчет массивов?   -  person Russel    schedule 18.01.2011
comment
@ Рассел, C и C++ - разные языки. Выбери один :)   -  person    schedule 18.01.2011
comment
@ Рассел, прочитай пост Евро Мичелли.   -  person    schedule 18.01.2011
comment
@Russel: Массивы также находятся в стеке, если они не выделены динамически с помощью new или malloc.   -  person Benjamin Lindley    schedule 18.01.2011
comment
@PigBen очень хорошо говорит о распределении массивов. Это неочевидная разница с C#.   -  person Euro Micelli    schedule 18.01.2011
comment
Я хотел бы подчеркнуть, что вы никогда, никогда не должны смешивать new и free() или смешивать malloc() и delete в одном и том же выделенном объекте, даже для тривиальных классов. new и delete не обязаны использовать одну и ту же кучу или те же библиотеки управления кучей, что и malloc и free(), поэтому смешивание двух наборов функций распределения является неопределенным поведением. Часто new/delete реализуются через вызовы malloc()/free(), но это не гарантируется, и легко создать пример C++, где это не так.   -  person Euro Micelli    schedule 18.01.2011


Ответы (6)


Похоже, вы приближаетесь к С++ с управляемого языка, такого как С#. В C++ все немного иначе.

То, что вы описываете как ссылочные типы, не существует в С++. Типы в C++ не являются «ссылочными типами» и не являются «типами значений». Они просто «типы». Обрабатываются ли они с помощью ссылок (или указателей) или по значению, полностью зависит от кода, который использует тип (а не определение типа). Напротив, в таких языках, как C#, оператор объявления типа решает, должен ли тип обрабатываться как ссылка или как значение. В C++ есть что-то, что называется ссылка, но оно не имеет ничего общего с тем, что вы описываете. В конце я упомяну ссылки на C++.

Теперь давайте посмотрим, сможем ли мы обработать несколько частей вашего вопроса:

Указатель помещается в стек, а фактические данные, на которые указывает указатель, создаются и помещаются в кучу.

Может быть. Это было бы верно, если бы вы создали такой объект, например:

class MyClass { /* ... */ };
...

MyClass* pObj1 = new MyClass();
MyClass* pObj2 = (MyClass*)malloc( sizeof(MyClass) );

Но нет, если вы создадите объект следующим образом:

MyClass obj3;

В последнем случае объект размещается в стеке, и указатель или ссылка не используются. Вы манипулируете им как «типом значения».

MyClass *pObj3 = &obj3;

Теперь pObj3 является указателем (в стеке) на obj3, который также находится в стеке. Видеть? Нет связи между определением класса и местом хранения объектов этого класса. Это зависит от того, как вы используете тип. Довольно часто используется комбинация объектов одного и того же типа, основанных на стеке и куче.

Стандартные массивы и определяемые пользователем классы являются ссылочными типами. Это верно?

Нет; массивы — это просто набор объектов одного типа/размера, размещенных в последовательных ячейках памяти. Массив может быть размещен в стеке или в куче, как и в случае с отдельными объектами.

C и C++ не придают массивам какой-либо особой семантики (за одним исключением, о котором я упомяну чуть позже). После того, как они распределены, они представляют собой просто набор объектов, которые оказались последовательными. Ваша программа может использовать операции с массивами или прямые операции с указателями для доступа к отдельным членам. Это означает:

  • arrayOfClass[i] точно идентично слову ((Class*)*(array + i)). В C вы даже можете сказать i[arrayOfClass], и это означает то же самое, что и arrayOfClass[i] (однако C++ будет жаловаться, потому что у него более строгие правила типов).
  • Вы можете использовать операторы массива в указателях на объекты, которые не являются частью массива (и, вероятно, произойдет сбой)
  • Вы можете использовать обычные операции с указателями над элементами массива.
  • Вы можете выделить «большой» кусок памяти и «создать свой собственный массив», интерпретируя меньшие последовательные фрагменты этой памяти, как если бы они были членами массива меньших объектов (именно это вы получаете, когда используете malloc()).

Массивы не являются типами сами по себе; это просто удобный способ размещения нескольких объектов и более удобный в некоторых обстоятельствах способ создания указателей.

Единственным исключением из этого правила «массивы не являются специальными», которое приходит мне на ум, являются массивы case, выделенные в C++ через new. Когда вы выделяете массив через new, он оставляет информацию (обычно в куче, примыкающей к массиву, но это не обязательно) о том, сколько элементов содержалось в массиве, когда он был выделен. Затем вы должны использовать специальный оператор delete [] для удаления массива. delete [] находит и использует этот дополнительный бит информации для правильного удаления всех элементов массива.

Во-вторых, мой главный вопрос: всегда ли механизмы управления памятью c и c++ (malloc, free and new, delete) обрабатывают это правильно и освобождают память, на которую указывает класс или массив?

Пока вы делаете все правильно, да.

Все ли по-прежнему работает, если эти указатели каким-то образом переназначаются другим объектам того же размера/типа в куче?

Да для free(), хотя использование указателей на другой тип при вызове free() (кроме void*) довольно необычно. Есть законные способы использования таких вещей, но это продвинутые темы. Возможно, вы захотите, чтобы опытный разработчик рассмотрел ваш дизайн, чтобы убедиться, что это действительно уместно.

delete это другое дело; если вы используете указатель на тип, отличный от того, что хранится в буфере во время вызова удаления, поведение будет "неопределенным" (например, вы, скорее всего, столкнетесь с ошибкой). Это потому, что delete делает больше, чем free(); он также вызывает метод деструктора объекта, и компилятор полагается на тип указателя для вызова надлежащего метода. Если вы используете неправильный тип указателя, будет вызван неправильный метод, и кто знает, что произойдет. Вы можете потенциально поместить что-то «еще» в буфер после того, как вы new сделали это, но это может потребовать нетривиального объема работы и снова является сложной темой.

Также обратите внимание, что вы никогда не должны выделять с malloc() и освобождать с delete, а также не должны выделять с new и освобождать с free(). Убедитесь, что ваши методы правильно подобраны.

Что, если в классе есть член-указатель, указывающий на другой объект? Я предполагаю, что удаление/освобождение объекта класса не освобождает то, на что указывает указатель члена, это правильно?

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

Все это предполагает, что объект владеет содержимым, на которое указывает указатель члена. В управляемых языках, таких как C#, все объекты «принадлежат» среде выполнения и удаляются под контролем сборщика мусора, поэтому вам не нужно об этом беспокоиться. В С++. кто «владеет» объектами, на которые указывают точки-члены, определяется семантикой вашей программы, а не языком, и вы должны обратить внимание, чтобы решить, когда настало подходящее время для удаления встроенных объектов. Если вы упустите нужный момент для удаления объекта, произойдет утечка памяти; если вы удалите его слишком рано, вы получите неопределенное поведение и сбой (когда какой-то код пытается использовать объект, который уже был удален).

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

С вашей точки зрения ссылка на С++ — это просто указатель, который выглядит как объект стека.

CMyClass pObj1 = new CMyClass();
CMyClass& myObject = pObj1;      // Create a reference to the object pointed by pObj1

pObj1->Method1();                // Use the pointer to call Method1
pObj1.Method1();                 // Use the reference to call Method1

Учитывая ваш уровень знаний в C++, я мог бы пока воздержаться от ссылок, пока вы немного не поймете управление памятью в C/C++.

person Euro Micelli    schedule 18.01.2011
comment
Это хорошее и глубокое объяснение. +1 - person ; 18.01.2011

Я предполагаю, что удаление/освобождение объекта класса не освобождает то, на что указывает указатель члена, это правильно?

Это правильно по умолчанию, поэтому классы C++ имеют деструкторы. Например:

class C {
    int* x;

public:
    C () : x (new int[10]) {}  // constructor
};

C* cptr = new C;  // a pointer to a C object

Если я удалю cptr, произойдет утечка памяти, так как x не будет освобожден. Поэтому я добавлю деструктор к C:

class C {
    ...
    ~C () {delete [] x;}  // destructor
};

(С остальной частью вашего вопроса есть масса проблем. Кажется, вы перепутали Java и C++, поэтому большая часть вашего вопроса не имеет смысла. Я ответил только на этот фрагмент, чтобы дать вам пример того, как бесплатные ресурсы автоматически. Я предлагаю вам прочитать книгу по C++, чтобы лучше понять язык.)

person chrisaycock    schedule 18.01.2011

Я не уверен, с чего начать.

У нас есть примитивные типы (например, int).

У нас есть наш старый друг — структура c.

У нас есть занятия.

Все это может быть автоматическим классом хранения; просто сидит в стеке. Все можно передать по значению.

Тогда у нас есть указатель-x. (x *). Они хранят адрес элемента в стеке или выделены где-то еще, как в случае с new. Получив указатель, вы должны убедиться, что вы не сделаете что-то, что сделает его недействительным. Если вы возвращаетесь из функции, указатели на автоматические элементы в ее динамическом контексте становятся недействительными. Если вы используете delete, указатель, через который вы удаляете, больше недействителен, как и его копии.

Тогда, наконец, у нас есть ссылки. x& - это просто синтаксический сахар. Передача ссылки на что-то — это просто передача указателя на это. Использование ссылочного типа избавляет от необходимости вводить несколько * символов и гарантирует, что указатель-под-таблицей никогда не будет нулевым.

person bmargulies    schedule 18.01.2011

Указатель помещается в стек, а фактические данные, на которые указывает указатель, создаются и помещаются в кучу.

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

Пример :

class Foo{
    public:
    // For Test1 This is in the Stack
    // For Test2 this is in the free store
    int x; 

    // For Test1 the pointer is in the Stack
    // AND -> It points, to where we set it (could be stack or heap)
    // For Test2 the pointer-variable-location is in the Free Store
    // AND -> Similar
    int *y;
}

int main()
{
    // Lies in the Local Scope
    Foo Test1;
    // Test2 Lies in the Local Scope, and contains the address of the 
    // Object, which is now on the Free Store
    Foo *Test2 = new Foo();
}

мой главный вопрос: всегда ли механизмы управления памятью c и c++ (malloc, free и new, delete) обрабатывают это правильно и освобождают память, на которую указывает класс или массив?

Прежде всего, старайтесь избегать смешивания бесплатных и удаленных. Во-вторых, free() получает указатель и не проверяет, указывает ли предоставленный указатель на допустимое место в памяти. Это означает, что вы можете попытаться освободить нераспределенную память, что может привести к Seg Faults. Стандартный пример этого

int main()
{
     int * x; // Points to random
     free(x); // Undefined behaviour
}

Другим неправильным использованием указателя может быть:

int main()
{
     int * x = malloc(sizeof(int) * 10); //Allocate an array of 10
     ++x; // Now points to x[1];
     free(x); // Give me trouble
}

Все ли по-прежнему работает, если эти указатели каким-то образом переназначаются другим объектам того же размера/типа в куче?

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

Что, если в классе есть член-указатель, указывающий на другой объект? Я предполагаю, что удаление/освобождение объекта класса не освобождает то, на что указывает указатель члена, это правильно?

free()ing класс не вызывает деструктор, и вы можете использовать деструктор, чтобы избежать утечек памяти. Используя delete, вы можете настроить деструктор на удаление этого другого объекта, иначе это вызовет утечку памяти.

Ваш пост говорит мне, что вы кое-что путаете, вы начинаете говорить о ссылках в C++, но в конечном итоге говорите об указателях и free() и delete, и в целом создается впечатление, что вы запутались. Я думаю, тебе стоит купить хорошую книгу.

person Community    schedule 18.01.2011

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

class dog {
    int age;
    char *name;
};

int main() {
    int x;
    dog barry;
    ...
}

Тогда внутри main, barry и x будут рядом друг с другом в стеке. Таким образом, стек будет содержать два целых числа и указатель на char.

Вы правы, говоря, что освобождение объекта не освобождает память, которую делают его указатели на члены. C++ не выполняет никакого автоматического управления памятью, поэтому вам придется выделять и освобождать все вручную. В этом случае лучше всего дать собаке деструктор, что-то вроде

class dog {
    ~dog() {
        delete name;
    }
}

~dog будет вызываться, когда вы удаляете собаку или когда собака выходит из области видимости и удаляется из стека.

person Shum    schedule 18.01.2011

Ссылка — это в основном постоянный указатель, который использует синтаксис автоматической переменной. Вы не можете изменить то, на что он указывает, и вы не можете использовать его для удаления объекта (напрямую; вы можете использовать оператор адреса для ссылки, чтобы получить указатель на указанный объект, а затем удалить его).

Функции malloc и free просто выделяют и освобождают блоки памяти. Обычно вам не нужно использовать их непосредственно в C++. Операторы new и delete выделяют и освобождают память, но они также вызывают конструктор и деструктор объекта. В конструкторе класса вызываются все конструкторы автоматических членов, то же самое и с деструкторами. Если в вашем классе есть член, являющийся указателем, его память не выделяется и не освобождается автоматически. Вы должны явно сделать это в конструкторе и деструкторе или использовать интеллектуальный указатель, например std::auto_ptr.

Когда оператор delete используется для указателя, вызывается деструктор для типа указателя, поэтому, если вы заставляете указатель указывать на неправильный тип с явным приведением, то при его удалении будет вызван неправильный деструктор.

person Alex    schedule 18.01.2011