포인터와 동적 메모리 관리
1. 정적 메모리 할당과 동적 메모리 할당 비교
정적 메모리 할당은 일반적으로 우리가 하는 변수 선언을 말합니다. 컴파일 단계에서는 stack/data 영역에 할당되면서 크기가 결정되므로 컴파일 이후 크기가 변경될 수 없습니다. stack영역에는 지역변수와 매개변수가 저장됩니다. 블록이 끝나면 소멸됩니다. data영역에는 전역 변수가 저장됩니다. 프로그램이 종료될때 사라집니다.
동적 메모리 할당은 프로그램 실행 단계에서 heap 영역에 할당됩니다. 이 heap의 자유저장소는 프로그래머가 원할 때, 원하는 크기로 할당이 가능하며 프로그래머에 의해 소멸되지 않으면 프로그램이 종료될 때 사라집니다. heap과 stack 영역 사이에는 완충지대가 존재하여 서로 메모리가 섞이는 일이 없습니다.
2. 동적 메모리 할당
동적 메모리 할당이란 프로그램 실행 도중에 필요한 만큼의 메모리를 할당하는 것입니다. c++에서는 new 연산자를 이용해 동적으로 메모리를 할당할 수 있으며 이는 heap에 저장됩니다.
int* p = new int;
delete p;
동적할당에서 포인터는 매우 중요한 역할을 합니다. 오로지 포인터를 통해서만 동적할당한 메모리에 접근할 수 있습니다.
#include <iostream>
using namespace std;
int main(){
int* pt = new int;
*pt = 3;
cout << "*pt = " << *pt <<endl;
return 0;
}
//*pt = 3
3. 배열의 동적 메모리 할당
new 연산자를 이용해 동적배열을 생성하면 배열의 크기를 실행 시에 결정할 수 있습니다. 동적 배열은 배열의 크기에 변수를 사용할 수 있습니다.
int* list =new int[size];
delete [] list;
#include <iostream>
using namespace std;
int main(){
int length;
cout <<"몇 개의 배열을 생성하겠습니까: ";
cin >> length;
int* p = new int[length];
for(int i=0; i< length;i++)
p[i] = i;
for(int i=0; i< length;i++)
cout <<p[i] << " ";
return 0;
}
//결과
몇 개의 배열을 생성하겠습니까: 5
0 1 2 3 4
4. 동적 메모리 해제
동적할당된 메모리는 영구적으로 남아있기 때문에 반드시 delete 이름; 나 delete [] 이름;로 소멸시켜주어야 합니다. 배열에서는 특히나 중요합니다. delete [] list; 를 이용하며 전체 메모리를 삭제해주어야 합니다.
하지만 new 연산자에 의해 생성된 메모리를 가리키지 않는 포인터를 delete로 삭제하면 오류가 발생합니다. delete와 new는 항상 짝이되어야 합니다.
동적할당된 메모리를 해제하지 않고 재할당하게 되면 그 메모리에는 다시는 접근할 수 없습니다. 이것을 메모리 누수(memeory leakage)라고 합니다.
delete로 삭제된 메모리를 가리키는 포인터는 정의되지 않은 상태가 되고 이를 허상포인터(dangling pointer)라고 합니다. 허상포인터에는 역참조 연산자를 사용해서는 안됩니다. 이를 방지하기 위해 삭제된 메모리를 가리키는 포인터에는 NULL을 할당해줍니다.
int*p = new int;
delete p;
p =NULL;
int* parr= new int[size]; //size는 변수
delete[] parr;
parr = NULL;
#include <iostream>
using namespace std;
int main(){
int* pt = new int;
*pt = 4;
cout << "*pt = " <<*pt <<endl;
delete pt;
pt = NULL;
int length;
cout <<"몇 개의 배열을 생성하겠습니까: ";
cin >> length;
int* p = new int[length];
for(int i=0; i< length;i++)
p[i] = i;
for(int i=0; i< length;i++)
cout <<p[i] << " ";
delete [] p;
p =NULL;
return 0;
}
객체와 포인터
1. 객체와 포인터
객테에 대해서도 일반 변수와 같은 방식으로 포인터를 사용할 수 있습니다.
#include <iostream>
using namespace std;
int main(){
Circle c1(4); // 객체를 가리키는 포인터 변수
Circle* cp = &c1;
cout << "radius: " <<(*cp).getRadius() << endl;// 괄호 반드시
Circle cArray[6]; //객체의 배열을 가리키는 포인터 변수
Circle* ca = cArray;
for(int i =0;i<6;i++){
ca[i].setRadius(i+1);
cout <<ca[i].getRadius()<<endl;
}
return 0;
}
2. 동적 객체 생성과 접근
인수없는 생성자를 사용한 동적 객체 생성에는 두가지 방법이 있습니다.
Circle* cp = new Circle ();
Circle * cp = new Circle ;
인수있는 생성자를 이용한 동적 객체 생성은 이렇게 합니다.
Circle* cp = new Circle(4);
객체의 동적 배열은 이렇게 생성합니다.
Circle* cp = new Circle[10];
객체 멤버에 접근하는데에 .연산자를 사용했습니다. 포인터에도 (*p).getRadius()같은 방식으로 사용할 수 있지만 p->getRadius(); 로도 사용할 수 있습니다. 이것을 화살표 연산자라고 합니다.
int main() {
Circle *cp = new Circle(4); //동적객체 생성
cout << "radius: " << (*cp).getRadius() << endl;
cout << "area: " << cp->getArea() << endl;
//1)사용자한테 배열크기를 입력받아 객체배열을 동적생성 해보기
int size;
cout << "몇 개의 배열을 생성하겠습니까 : " ;
cin >> size;
Circle* ca = new Circle[size]; //객체의 동적배열 생성
for (int i = 0; i < size; i++) {
ca[i].setRadius(i + 1);
cout << ca[i].getRadius() << "\t" ;
//2) ->를 이용해 getRadius()함수에 접근하기
cout << (ca+i)->getRadius() << endl;
}
return 0;
}
3. this 포인터
this 포인터는 객체 자신을 가리킵니다.(객체의 주소를 가리킴)
set()함수나 인자있는 생성자에서 종종 매개변수와 멤버변수에 같은 이름을 사용하는 데 이때 이를 구별하기 위해 사용하기도 합니다.
Circle::Circle() {
this->radius = 1;
}
Circle::Circle(double radius){
this->radius = radius; // or (*this).radius = radius;
}
double Circle::getArea(){
return radius * radius * 3.14159;
}
double Circle::getRadius(){
return radius;
}
void Circle::setRadius(double radius){
this->radius = (radius >= 0) ? radius : 0;
}
4. 소멸자(destructor)
소멸자는 객체가 삭제될 떄 자동으로 호출되는 c++의 특이한 함수입니다. 생성자처럼 클래스와 이름이 같고 ~틸더 문자를 붙여 만듭니다. 반환유형과 인수가 없습니다. 항상 1개 존재합니다. 소멸자가 명시적으로 정의되지 않은 경우, 컴파일러가 기본 소멸자를 만들어줍니다.때로는 사용자 요구 동작을 수행하기 위해 소멸자 안의 코드를 구현하는 경우가 있지만 보통 기본 소멸자를 사용합니다.
//Circle.h
#ifndef CIRCLE_H
#define CIRCLE_H
class Circle {
public:
Circle();
Circle(double);
~Circle(); // 소멸자
double getArea();
double getRadius();
void setRadius(double);
static int getNumberOfObjects();
private:
double radius;
static int numberOfObjects;
}; // 세미콜론 주의
#endif
//Circle.cpp
#include “Circle.h”
int Circle::numberOfObjects = 0; //정적멤버 초기화
Circle::Circle() {
this->radius = 1;
numberOfObjects++;
}
Circle::Circle(double radius){
this->radius = radius;
numberOfObjects++;
}
Circle::~Circle() { //소멸자
numberOfObjects--;
}
double Circle::getArea(){
return radius * radius * 3.14159;
}
double Circle::getRadius(){
return radius;
}
void Circle::setRadius(double radius){
this->radius = (radius >=0)?radius : 0 ;
}
int Circle::getNumberOfObjects() {
return numberOfObjects;
}
#include <iostream>
#include "Circle.h"
using namespace std;
int main()
{
Circle* pc1 = new Circle();
Circle* pc2 = new Circle(5.0);
Circle* pc3 = new Circle(2);
cout << "원의 개수: "<< Circle::getNumberOfObjects() << endl;
delete pc1;
pc1 = NULL;
cout << "원의 개수: "<< Circle::getNumberOfObjects() << endl;
Circle *ca = new Circle[10];
cout << "원의 개수: "<< Circle::getNumberOfObjects() << endl;
delete[] ca;
ca = NULL;
cout << "원의 개수: "<< Circle::getNumberOfObjects() << endl;
return 0;
}
11.20) double값에 대한 메모리 공간은 어떻게 생성하는 가? 이 double 값에는 어떻게 접근 하는가? 이 메모리는 어떻게 삭제하는 가?
double* mem = new double;
*mem = 6.2;
cout << *mem << endl;
delete mem;
11.21) 동적 메모리는 프로그램이 종료될 때 삭제되는가?
예
11.22) 메모리 누설에 대해 설명하라.
동적할당한 포인터를 삭제하지 않고 또 동적할당했을 때 원래 동적할당되었던 메모리가 접근할 수도 삭제할 수도 없게 되는 것을 말한다.
11.23) 동적 배열을 생성하고 나중에 그것을 삭제해야 한다고 가정하자. 다음 코드에서 두 가지 오류를 찾아보아라.
double x[] = new double[30];
...
delete x;
답)
double x[] = new double[];
delete [] x;
로 바꾸어야 한다.
11.24) 다음 코드에서 잘못된 부분은 무엇인가?
double d = 5.4;
double* p1 =d;
포인터에는 값이 아니라 주소를 할당해야 한다. double* p1 =&d;라고 해야 한다.
11.25) 다음 코드에서 잘못된 부분은 무엇인가?
double d = 5.4;
double* p1 =&d;
delete p1;
동적할당하지 않고 delete했다.
11.26) 다음 코드에서 잘못된 것은 무엇인가?
double* p1;
p1* = 5.4;
포인터의 초기화를 따로 하고 있다.
double* p1 =new double;
*p1 = 5.4;
로 바꿔야 한다.
11.27) 다음 코드에서 잘못된 것은 무엇인가?
double* p1 = new double;
double* p2 = p1;
*p2 = 5.4;
delete p1;
cout << *p2 << endl;
답)
이미 삭제된 메모리를 가리키고 있다.
double* p1 = new double;
double* p2 = p1;
*p2 = 5.4;
cout << *p2 << endl;
로 바꾸어야 한다.
11.28) 다음 프로그램은 올바른 프로그램인가? 만일 그렇지 않다면 수정하여라.
(a)
int main()
{
string s1;
string* p = s1;
return 0;
}
int main()
{
string s1;
string* p = &s1;
return 0;
}
로 고쳐야 한다.
(b)
int main()
{
string* p = new string;
string* p1 = new string();
return 0;
}
맞다.
(c)
int main()
{
string* p = new string("ab");
return 0;
}
맞다.
11.29) 객체를 동적으로 어떻게 생성하는가? 어떻게 삭제하는 가?
(a)코드가 잘못된 이유와 (b)코드가 올바른 이유에 빗대어 설명하시오.
//a
int main()
{
string s1;
string* p = &s1;
delete p;
return 0;
}
//b
int main()
{
string s1;
string* p = new string();
delete p;
return 0;
}
a는 new연산자를 이용한 동적할당이 일어나지 않은 채 delete했기 때문에 틀렸다. 객체 포인터에 new 객체이름();나 new 객체이름; 로 동적 객체를 생성한다.
11.30) 다음 코드에서 7번과 8번 줄(new) 모두 익명 객체를 생성하고 원의 면적을 출력한다. 8번 줄이 잘못된 이유는 무엇인가?
#include <iostream>
#include "Circle.h"
using namespace std;
int main()
{
cout << Circle(5).getArea() << endl;
cout << (new Circle(5))->getArea() << endl;
return 0;
}
동적할당은 익명객체로 만들면 안된다. 프로그램이 끝날때까지 저장되어 있고 포인터만 삭제된다.
11.31) 다음 코드에서 잘못된 부분을 수정하라.
Circle::Circle(double radius){
radius = radius}
수정된 코드 ▼
Circle::Circle(double radius) {
this->radius = radius;
}
11.32) 모든 클래스에는 소멸자가 포함되어 있는가? 네
소멸자의 이름은 어떻게 되는가? 클래스이름과 동일
소멸자는 오버로딩이 가능한가? 소멸자는 한개만 존재하며 매개변수를 가지지 않는다.
소멸자를 재정의 할 수 있는가? 가상 함수로 소멸자가 사용되었다면 자식 클래스에서 재정의 될 수 있다.
명시적으로 소멸자를 호출할 수 있는가? 네
11.33) 다음 코드의 출력은 무엇인가?
#include <iostream>
using namespace std;
class Employee {
public:
Employee(int id) {
this->id = id;
}
~Employee() {
cout << "object with id " << id << " is destroyed" << endl;
}
private:
int id;
};
int main()
{
Employee* e1 = new Employee(1);
Employee* e2 = new Employee(2);
Employee* e3 = new Employee(3);
delete e3;
delete e2;
delete e1;
return 0;
}
//출력
object with id 3 is destroyed
object with id 2 is destroyed
object with id 1 is destroyed
11.34) 다음 코드에서 소멸자가 필요한 이유는 무엇인가? 소멸자를 추가하여라
class Person {
public:
Person() {
numberOfChildren = 0;
children = new string[20];
}
void addAChild(string name) {
children[numberOfChildren++] = name;
}
string* getChildren() {
return children;
}
int getNumberOfChildren() {
return numberOfChildren;
}
private:
string* children;
int numberOfChildren;
};
new를 이용해 child를 동적할당하기 때문에 delete로 지워주지 않으면 메모리 누수(memory leak)가 계속 발생한다.
class Person {
public:
Person() {
numberOfChildren = 0;
children = new string[20];
}
~Person() {
delete [] children;
numberOfChildren--;
}
void addAChild(string name) {
children[numberOfChildren++] = name;
}
string* getChildren() {
return children;
}
int getNumberOfChildren() {
return numberOfChildren;
}
private:
string* children;
int numberOfChildren;
};
#include <iostream>
using namespace std;
#include "Course.h"
class Course {
public:
Course(const string& courseName, int capacity){
this->courseName = courseName;
this->capacity = capacity;
numberOfStudents = 0;
students = new string[capacity];
}
~Course(){
delete[] students;
}
string getCourseName() const {
return courseName;
}
void addStudent(const string& name) {
students[numberOfStudents++] = name;
}
void dropStudent(const string& name) {
numberOfStudents--;
}
string* getStudent() const {
return students;
}
int getNumberOfStudents() const {
return numberOfStudents;
}
private:
string courseName;
string* students;
int numberOfStudents;
int capacity;
};
int main()
{
Course course1("Data Structures", 10);
Course course2("Database Systems", 15);
course1.addStudent("Peter Jones");
course1.addStudent("Brian Smith");
course1.addStudent("Anne Kennedy");
course2.addStudent("Peter Jones");
course2.addStudent("Steve Smith");
cout << "Number of students in course 1: " <<
course1.getNumberOfStudents() << endl;
string* students = course1.getStudent();
for (int i = 0; i < course1.getNumberOfStudents(); i++)
{
if (i == course1.getNumberOfStudents() - 1)
cout << students[i] << endl;
else
cout << students[i] << ", " << endl;
}
students = course2.getStudent();
cout << "\nNumber of students in course 2: "
<< course2.getNumberOfStudents() <<endl;
for (int i = 0; i < course2.getNumberOfStudents(); i++)
{
if(i== course2.getNumberOfStudents()-1)
cout << students[i] << endl;
else
cout << students[i] << ", " << endl;
}
course2.dropStudent("Steve Smith");
cout << "\nNumber of students in course 2: "
<< course2.getNumberOfStudents() << endl;
for (int i = 0; i < course2.getNumberOfStudents(); i++)
{
if (i == course2.getNumberOfStudents() - 1)
cout << students[i] << endl;
else
cout << students[i] << ", " << endl;
}
return 0;
}
string* students는 배열을 말합니다. 어떤 것이 들어올 지 모르므로 생성자 함수에서 동적할당 해주고, 이를 소멸자로 지워줍니다.
11.35) Course 객체가 생성될때, students 포인터의 값은 무엇인가?
배열
11.36) delete [] students 가 students 포인터에 대한 소멸자의 구현에 사용된 이유는 무엇인가?
student 포인터가 배열이기 때문이다.
11.38) 다음 코드의 출력은 무엇인가?
#include <iostream>
#include <string>
using namespace std;
int main() {
string s1("ABC");
string s2("DEFG");
s1 = string(s2);
cout << s1 << endl;
cout << s2 << endl;
return 0;
}
DEFG
DEFG
11.39) s1 = string(s2);는 다음 코드와 동일한가?
s1=s2;
어느 것이 더 좋은가?
동일하지 않다. s1 = string(s2);은 얕은 복사가 되고 s1=s2;은 깊은 복사가 된다. 상황에 따라 다르지만 메모리 면에서는 s1 = string(s2);
'대학강의정리 > 24.1 고급 c++' 카테고리의 다른 글
9주차 ch15 상속과 다형성 앞부분 (0) | 2024.05.07 |
---|---|
7주차 템플릿, 벡터, 스택 (0) | 2024.04.20 |
5주차 11장 포인터와 동적 메모리 관리 1,2 (0) | 2024.04.18 |
4주차 10장 객체 지향 개념 3,4,5 예상문제 (0) | 2024.04.18 |
3주차 10장 객체 지향 개념 1,2 예상문제 (0) | 2024.04.15 |