관리 메뉴

사적공간

홍정모C++(섹션 5~ 섹션 17 6.11 메모리 동적할당 new 와 delete) 본문

KNOU_CS/C++

홍정모C++(섹션 5~ 섹션 17 6.11 메모리 동적할당 new 와 delete)

2sac 2024. 11. 30. 10:31

지역변수의 범위와 지속 기간 (scope,duration)

동적 할당을 하면 범위와 지속 기간이 분리됨.

 

 

  선언은 여기저기 가능. 정의는 한 곳에서 나와야 함. 

  

scope resolution operation = '::' 

이렇게 네임스페이스를 활용하여 이름이 같은 변수나 함수를 활용 가능
이렇게 네임스페이스::변수(함수) 로 호출해서 씀.

 

 

 

c++ 17 에선 아래와 같은 문법사용 가능 

우측 사이드 우클릭 프로퍼티에서 c++ 버전 변경


전역변수, 정적변수, 내부연결, 외부연결 

전역변수는 가급적 수를 줄여야 함. 

 

 

 

 

전역변수는 잘 안 쓰게 되는데, 다른 파일에 전역변수가 값을 바꾼다면 감당이 안됨. 

 

이름으로 구분짓거나 설계를 잘 하거나 객체지향으로 전역변수를 안쓰거나 

 

 

static으로 막아놓으면 다른 cpp 파일에서 접근 불가

 

 

 

 

 

 

다른 파일에 정의된 함수를 포워드 디클러레이션(전방선언)으로 불러옴. 어딘가에 몸체가 있다.
앞에 extern이 생략되어 있는 것임.

 

 

 

 

 

이렇게 외부파일에 정의되어 있어 

 

 

 

 

 

 

 

 

 

 

 

쓰려고 하지만 몸체를 링커가 못찾아서

해결은? 

정의된 파일에 가서 변수를 초기화를 해주면 됨. 

만약 익스턴이 정의된 파일에서 한 번 더 초기화를 해주면 에러가 뜸. 

 

같은 cpp파일을 참고했지만 메모리 주소가 다름
cpp 파일을 하나 더 만들어 초기화 하고, 헤더파일은 초기화만 지우고, extern 추가해준후 돌리니까 주소가 동일

 

 

각각 다른


Using문과 모호성 

 

cout 만 쓸 때, cout은 std에 있음. std는 iostream에 있음.

 

위에 a,b 공간을 만들고 이렇게 사용하는 것도 가능함.

 

 

my_var가 두 네임스페이스의 영향을 받아서 애매모호함.  

깔끔하게 정리

 

using namespace는 큰 영역에 넣으면 큰 오류를 초래할 수 있다. 좁은 영역에 넣고 쓰는게 좋음. 

 


auto 키워드와 자료형 추론 

 

함수에서 auto 활용

 

 

함수의 매개변수에는 auto를 못 씀. 내가 넣는 실 매개변수에 따라 함수에 들어가는 자료형이 결정되게끔 하려면 auto가 아니라 템플릿을 써야 함. 

 


임시형 형변환(coersion)과 명시적 형변환(casting)

데이터의 타입을 말해줌.

 

 

더 큰데로 가는 경우(자료형이) : 뉴메릭 프로모션

 

큰것에서 작은것으로 절삭됨.

 

 

char 범위 내라서 2 그대로 출력됨.

 

 

 

 

C++에서 static_cast<int>(c)와 (int)c는 둘 다 형 변환을 수행하지만, 작동 방식과 의미에는 차이가 있습니다.


1. (int)c (C 스타일 캐스트)

  • C 스타일 형 변환으로, C++에서도 사용할 수 있습니다.
  • 모호하고 위험합니다.
    • 여러 종류의 캐스트(static_cast, reinterpret_cast, const_cast, dynamic_cast)를 모두 포함할 수 있으며, 컴파일러가 적절한 변환을 선택합니다.
    • 코드에서 의도를 명확히 알기 어렵습니다.
    • 변환 과정에서 예기치 않은 동작이 발생할 수 있습니다.
  • 용법:
    char c = 'A';
    int i = (int)c;  // C 스타일 캐스트
    

2. static_cast<int>(c) (C++ 스타일 캐스트)

  • C++에서 도입된 명시적 형 변환 방식입니다.
  • 안전하고 명확합니다.
    • 변환이 명시적으로 컴파일 타임에 검증됩니다.
    • 불필요하거나 위험한 변환은 컴파일러가 경고하거나 오류를 발생시킬 수 있습니다.
    • 어떤 종류의 캐스트인지 명확히 드러나므로, 코드의 가독성과 유지보수가 더 쉽습니다.
  • 용법:
    char c = 'A';
    int i = static_cast<int>(c);  // C++ 스타일 캐스트
    

차이점 비교

특성 (int)c static_cast<int>(c)

역할 모든 형 변환 수행 가능 컴파일 타임 검증된 형 변환
안전성 낮음 높음
명시성 낮음 높음
사용 권장 C 스타일 코드에서 사용 C++ 스타일 코드에서 사용
가독성 낮음 높음

결론

C++ 코드에서는 **static_cast**를 사용하는 것이 권장됩니다. 이는 코드의 의도를 명확히 하고, 예상치 못한 동작을 방지하는 데 도움이 됩니다.
(int)c는 여전히 동작하지만, 모호하고 덜 안전하기 때문에 가급적 사용하지 않는 것이 좋습니다.

 

 

 

정밀도가 떨어짐..

 

정수형이라 정밀도를 높여도 소수점 아래 절삭 , 반올림은 따로 있음.

 

 

언사인드와 사인드의 변환이 저장시에 차이가 있어서 이런 결과를 만듦.

 

우선순위 낮은 것부터 높은 것으로

 


 문자열 소개  std::string  

 

 

 

 

사용자 정의 자료형.

 

getline은 엔터 칠때까지 쭉 입력을 받음.

 

 

문제가 되는 상황
실제로는 1 입력하고 이렇게 넘어감
같은 결과

 

 

 

 

 

  

 


열거형 enumerated types

 

BLUE 둘은 다른 enum 자료형에 속해있지만 컴파일 안됨

 

 

 

 

 

숫자를 지정하면 그 숫자부터 아래로 갈수록 커짐

 

 

이거 가능

 

 

 

 

 

캐스팅은 됨
받기도 안됨

 

   enum은 integer와 일부 호환된다. 그리고 casting을 활용한다. 

 


영역 제한 열거형 (열거형 클래스) scoped Enumerations (Enum Class)

 

 

 

캐스팅 해서는 가능함.

 

 

 

 


자료형에게 가명 붙여주기

 

 

 

읽기 쉬우니 유지보수 하기 쉽고, 고정너비 정수에서 사용? 

 


구조체 struct 

#include <iostream>
#include<iomanip>
#include<limits>
#include<string>
#include<cstdint>
#include<vector>
using namespace std;

struct person 
{
    double height;
    float weight;
    int age;
    string name;

};

void printPerson(person ps) {

    cout << ps.height << " " << ps.name <<" " << ps.weight <<" "<< ps.age;


}

int main() 
{
    person me;
    me.age = 20; 
    me.name = "Jack";
    me.weight = 50;
    me.height = 177; 
    person mom{ 170,50,23,"udi" }; // uniform 초기화랑 똑같네 
    person dad;
    printPerson(me); 



return 0;
}

 

 

 

. 은 멤버선택 오퍼레이터 연산자 

 

 

 

구조체 안에 함수를 넣고 

#include <iostream>
#include<iomanip>
#include<limits>
#include<string>
#include<cstdint>
#include<vector>
using namespace std;

struct person 
{
    double height;
    float weight;
    int age;
    string name;


    void print() {

        cout << height << " " << name << " " << weight << " " << age;


    }



};


int main() 
{
    person me;
    me.age = 20; 
    me.name = "Jack";
    me.weight = 50;
    me.height = 177; 
    person mom{ 170,50,23,"udi" }; // uniform 초기화랑 똑같네 
    person dad;
    //printPerson(me); 
    me.p


return 0;
}

구조체 me에 함수가 생긴걸 확인

 

 

 

이것도 가능

 

 

= 어싸인먼트가 생각대로 동작 안할 수도 .. 

 

 

구조체 안에 구조체를 넣을 수도 있음. 

 

 

 

함수를 호출해서 구조체 변수에 리턴값으로 받은 구조체를 넣을 수도 있음. 

 

 

 

 

구조체에 있는 초깃값으로 초기화를 할 수도 있음. 

기본값을 넣어줌.

 

초깃값이 기본값을 덮음.

 

 

컴퓨터가 데이터를 더 잘 배치하기 위해 공백의 2바이트를 더 씀. (이런걸 패딩이라고 함.)

 

 

최적화: 구조체 안에 데이터 변수의 순서나 기타 다른 것들을 바꿔서 컴퓨터가 더 빠르게 처리하게 좋게 만드는 것. 

 


제어 흐름 소개 

 

 

 

 

exit(0)은 디버깅할 때 많이 씀. 

return 보다 긴급하게 나감. exit(0);

 

여기서 강의 점프함. 

 


배열 기초[1 of 2] array

 

 

 

 

구조체 자료형으로 배열 만들기

 

 

배열 초기화 

 

 

enum과 함께 쓰기 .

 

 

 

 

이렇게 쓰면 학생수와 딱 맞게 됨. 

#include <iostream>

using namespace std;

enum studentName {
    JACK,
    DASH,
    VIO,
    NUM_STDUDENTS,
};


int main() 
{
    int student_scores[NUM_STDUDENTS];

    student_scores[JACK] = 0;
    student_scores[DASH] = 100;
    
return 0;
}

 

컴파일 -> 런타임 

 

런타임의 배열의 개수가 결정되어서 아래처럼 하며 안됨. 컴파일 시점에서 결정되도록 해야 함. 

 

 

 

C 스타일

 

 

const로 가능

const를 쓴다는 건 컴파일 타임 전에 5가 들어가는 것이 결정된다는 의미 


배열의 기초적인 사용법 [1/2]

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

사진 1

메인함수에서 찍은 배열의 시작주소와 함수호출로 찍은 배열의 시작주소가 다름 

-> 배열의 이름은 그 자체가 주소이기 때문에 앞에 &를 붙이지 않아도 주소를 구할 수 있음. // 그래서 배열의 시작주소의 경우 &를 붙인것과 뺀것의 결과는 같음. 

 

함수의 () 안에 들어가는 형식매개변수는 배열 모양을 하고 있지만 배열이 아님, 배열이 아니라 포인터임. 

그리고 마찬가지로 함수 안에 있는 배열의 변수처럼 보이는 것들도 배열 자체가 아니라 배열을 가리키고 있는 포인터임. 

 

그래서 위 '사진1'의 캡처본과 같은 결과가 나오는 것임. 

시작주소가 다르게 

 

 

 

그래서 함수 안에 있는 포인터 변수에 다시 &를 취해서 

주소를 가리키게 하면 메인함수와 호출하는 함수의 배열의 시작주소가 찍히게 되어 똑같은 값이 보이게 됨. 

 

 

그리고 배열과 포인터의 차이로 인해 사이즈를 찍어보아도 다르게 나오는 걸 알수 있음. 

 

x86(32비트)에선 포인터 변수의 사이즈가 4바이트

 

 

그러나 x64에선 포인터 변수의 사이즈가 8바이트 

 

 

배열과 포인터는 땔 수 없는 관계 

 

 

 


배열과 반복문 

 

나누기를 하기전과 한 후의 캐스팅은 값이 다를 수 있어 

 

 

 

배열 사이즈 구하기

함수로 구할 경우 포인터 변수로 구하는 것 주의 (위  내용)

 

 

 

가장 큰값 구하기 삼항연산자 

 

 

 


배열과 선택정렬 

 

 

값의 크기를 비교하고 첨자를 바꾸고, 값을 swap // 가독성은 나쁨

가볍게 보고 넘어감 

 


정적 다차원 배열 

 

2차원 배열은 동적 할당을 하게 됨. 

 

 행렬x행렬, 행렬x벡터를 2차원 배열로 구현해보기 (숙제) 

 


C언어 스타일의 배열 문자열 

겹따운표"" 문자열 마지막에 null 문자가 들어 있음.

 

 

 

널문자 전까지 출력하기 때문에

 

 

 

빈칸까지 포함해서 입력하고 출력가능 .

  

while문으로 출력

 

#include<cstring> 

 

source에 있는 문자열을 const 포인터로 받아서 dest에 붙여넣기 

 

 

데스트의 공간이 소스보다 작으면 공간침법이 일어나서

 

//strcat() 문자열 붙이기

// strcmp() 같은지 비교 

 


포인터의 기본적인 사용법

 

 

 

 

 

 

나에게 연락하려면 이분에게 연락하세요. 

이분이 포인터변수, 

 

 

*&x == x  // *와&가 만나서면 서로 소멸되는 것처럼 동작함. 

 

 

 

 

이렇게 할 수도 있음.

 

 

 

구지 왜 포인터를 쓸까? 

-> 가장 기본이 되는 이유는 배열 때문에 그렇음. 함수 파라미터로 넣으면 전부 복사가 되어서 부담됨. 그래서 포인터로 포인터 주소 시작위치와 개수를 던져줘서 찾아가게 함. // 이후 추가 설명 

 

 

 

 

이렇게 아예 안되진 않지만, 이게 또 해킹의 원리 

 

타입아이디로 데이터의 타입 찍어보기 . 

 

 

포인터 자체 사이즈는 고정,

 

 

구조체에 대한 포인터 

16바이트 구조체

 

 

 

 

 

실제 작업할 때는 WARNING이 많아서 초기화 안된 변수는 안보일 수 있음. 

 

디버거로 잡음. 

 

 

 


널 포인터 

포인터 변수에 이상한 주소값이 가리켜 있으면 문제되어서 있음. 

 

 

C스타일로 0을 넣기도 함. 

 

 

 

 

nullptr 만 받음.

 

 


포인터와 정적배열 

메모리 동적할당과 관련있음 

 

 

prt에 array를 넣어도 같은 곳을 가리키고 있는 것을 알 수 있음. ptr과 array 자체의 주소는 다름.

 

 

   

함수로 호출된 곳에서 찍어본 array도 4 왜나면 그냥 포인터이기 때문임.

 

 

 

함수 매개변수에 int array[ ] 와 int *array는 사실 같음. 

 

함수로도 역참조 해서 잘 나오는 거 확인 

 

 

 

함수로 배열을 전달받고, 함수에서 역참조로 배열의 시작주소에 있는 공간에 100을 넣음 

그리고 메인함수에서 다시 배열을 찍어봄 

 

 

 

바뀐 걸 확인할 수 있다. 

 

 

 

 

*(ptr+1) 이 7인것을 확인 

 

 

 

구조체 안에 배열을 넣고, 구조체를 보내버림. 

 

 

구조체에 함수를 보내보고, 구조체 안에 배열의 사이즈를 찍음 

 

 

이번에는 주소를 던져주고 포인터로 받아서 했지만 결과는 위와 동일 

 

 

 


포인터 연산과 배열 인덱싱 

 

집을 찾을 때, 301호의 옆집, 302호의 앞집 이런식의 개념임. 

 

uintptr_t 는 언사인드인티저포인트 타입으로 더 정확한 형변환 방식 

 

int형이라서 4바이트씩 변화하는 것을 알 수 있다.
double형이라서 8씩 바뀜

 

2씩 바뀜

 

 

 

for문으로

 

 

포인터로 구현하면 

 

 

 

long long 8씩 차이남.

 

문자열 

 

 

 

while문으로 해봄.

 


 

C언어 스타일의 문자열 심볼릭(기호적) 상수 

 

자료형 앞에 const 가 붙어야지 '기호적 상수'처럼 쓸 수 있음.

 

컴파일러가 너희 문자열이 같으니까 메모리를 같이 써라. 

 

글자를 바꾸면 주소도 달라짐. 

 

 

리턴타입에도 쓸 수 있다. 

 

 

문자포인터의 특성  

cout 에서 문자열은 특별하게 처리한다고 생각해야 함.

문자배열의 시작주소가 COUT을 만나면 주소를 찍지 않고, 문자열이 아닐까해서 안에 값을 NULL이 나올때까지 출력함. 

 

 

똑같은 이유임.

 


메모리 동적할당 new 와 delete

 

1.스테틱 메모리 얼로케이션, 전역, 스테틱.. 프로그램이 끝날때까지 가진것

2.자동메모리 할당: 변수 선언, 정적 배열 선언했을때, 블록 바깥에 나가면 사라지는 것 

3. 동적 메모리 할당 : 포인터와 관련지어 

 

 

정적 메모리 할당은 스택으로 가고 스택은 크기가 작고

동적 메모리 할당은 힙에 들어가고 힙은 크기가 큼. 

 

 

할당받은 메모리를 os에게 다시 돌려줘야 함. 

-> 컴퓨터 메모리를 효율적으로 쓰기 위해 

 

 

주의할 점 

딜리트 ptr 이 없어도, 오에스가 메모리를 엄마만큼 줄 걸 기억하고 있어서 자동으로 메모리를 걷어가서 문제가 안생김. 

딜리트 ptr은 os가 걷어가기 전에 미리 반납하겠다는 의미 

 

 

 

메모리는 이미 반납했는데, 주소는 변수에 따로 저장되는 거라서 그대로 따랐갔는데, 알수 없는 값이 나오게 됨. 

 

하지만.. 실제로 해보면.. 

달랐음. ide가 업뎃되면서 바뀐게 있나봄 

 

#include <iostream>
#include <typeinfo>

using namespace std;



int main() 
{

    int* ptr = new int{7}; // os에게 4바이트 공간을 받고 주소를 받음. 
    cout << ptr << endl;
    cout << *ptr << endl; 
   
    delete ptr; // os에게 메모리 반납 
    ptr = nullptr; // 아무런 의미도 없는 공간이에요. 기록 

    cout << "After delete" << endl; 
    if (ptr != nullptr) // ptr이 의미가 있을 때만 역참조를 할 수 있게 함. 
    {
        cout << ptr << endl;
        cout << *ptr << endl;
    }

return 0;
}

 

 

다른 프로그램이 메모리를 차지하고 있어서 메모리를 할당 받지 못하는 경우가 있음. 

그때 그냥 프로그램을 죽이거나 

기다렸다가 다시 할당받는 

 

메모리 할당에서 에러가 있는 경우 견디는 법 

nothrow (예외처리 방법 중 하나 )

 

 

ptr2도 반납해야 하는데, 안하면 에러 날 수 있어서 꼼꼼하게 하거나 안전장치 이용 

 

 

메모리 누수 

메모리를 받아서 만들기만 하고 쓰질 않음. 어디 있는지도 알 수 없음. 

이런 실수를 많이 함. (지우는 것을 깜빡함)

 

<확인방법> 

작업관리자에서 메모리 사용량이 Peak 를 찍으면서 느려짐.. 어딘가 메모리가 세고 있음 

 

 

 

 

debug > show windows > diagnostic tools.. 

 

메모리 사용량이 끝없이 올라가는 것이 보임. 

 

delete ptr; 을 넣으니 메모리를 반납하고 다시 받기를 반복해서 메모리 사용량이 수평을 그리는 것을 볼 수 있음 

 

new와 delete는 os를 다녀와야 해서 느리기 때문에 가능한 적게 사용하는게 프로그래밍을 잘 하는 것임. 

다른 언어들은 이걸 사용자가 통제할 수 없어서 느려지는 걸 관리할 수 없는 문제가 있음. c++ 은 통제할 수 있어서 최적화에 좋음. 단 실수하기 쉽고, 어려운 단점이 있음. 

 

 


동적 할당 배열