동적 메모리 할당


우리는 이제껏 메모리를 할당할때 정적으로 할당했습니다. 어떤 것이냐면


int arr[100];


이렇게 할당을 했었죠. 뭐 문제없습니다. 실행도 잘 되구요.


하지만 이런 상황은 조금 불편할 수 있겠죠.


● 처음 int배열 100개가 필요하다고 생각했는데 프로그램을 실행하다 보니 int배열이 500개만 필요한 경우


● int배열 500개가 모자라 배열 500개를 메모리에 더 할당해야 할 경우


우리는 메모리를 효율적으로 사용하기 위해서 너무 남거나, 너무 모자란 메모리 할당을 피해야할텐데요. 그 목적을 달성하기 위해서 이런 코딩은 어떨까요?




#include <stdio.h> int main() { int size, i; scanf("%d", &size); int arr[size]; for (i = 0; i < size; i++) scanf("%d", &arr[i]); for (i = 0; i < size; i++) printf("arr[%d] = %d\n",i,arr[i]); }


size를 입력으로 받고, 배열의 크기를 size만큼 할당하는 겁니다. 그리고 그 size만큼 for루프를 돌아 정수를 입력받고 있네요.

이 코드는 분명 얼핏 보기에는 문제가 없어보입니다. 그러나 메모리에 할당하는 시점을 고려한다면 이 코드는 실행조차 안되고 컴파일 에러가 발생하게 됩니다.


왜요?


stack에서 메모리는 컴파일 시점에서 결정됩니다. 우리는 실행중에 입력을 받고, 실행 중에 메모리를 할당해야하는데, 그 앞의 단계인 컴파일단계에서는 얼마만큼의 메모리를 할당할지 알 수 있는 방법이 없죠. 컴파일러는 모릅니다.


우선 우리는 메모리 구조에 대해서 조금 알아야 할 필요가 있습니다. 





스택영역

우리는 이제껏 스택영역에 메모리를 할당해 왔습니다. 컴파일 시점에 결정되는 영역입니다.


함수의 지역변수, 매개변수등이 이 메모리에 할당이 됩니다. 그리고 함수가 종료되었을때 할당된 메모리를 반환하게 됩니다.


그러니까 메인 함수안의 변수들은 모두 스택영역에 할당이 된거죠. 




힙 영역

이 영역의 메모리는 실행시점(Run Time)에 결정됩니다. 프로그래머에 의해서요. 이 영역을 힙영역이라고 합니다.


한가지 더 보충설명을 하자면 스택영역은 높은 주소에서 낮은 주소로 할당이 되고, 힙영역은 낮은 주소에서 높은 주소로 할당이 됩니다. 그래서 재귀함수를 통해 함수를 계속호출하게 되면 힙영역을 침범해 스택오버플로우가 발생하게 됩니다. 그 반대의 경우는 힙오버플로우가 발생합니다.


데이터 영역

이 영역의 메모리는 정적변수, 전역변수, 구조체 등 함수 외부에서 선언되는 변수들이 이 메모리에 할당됩니다.


코드 영역

코드영역에는 프로그램의 실행 명령어들이 존재합니다.


우리가 이번에 주목해야할 영역은 힙영역입니다. 위의 코드를 정상적이게 동작시키기 위해서는요.


그 목적을 달성하기 위해서 나온 함수가 바로 malloc함수입니다. malloc함수는 stdlib헤더에 선언되어 있으며 malloc함수를 사용하기 위해서는 stdlib.h를 include해야합니다.


void *malloc(size_t size)


이 함수는 size만큼의 메모리를 힙영역에 할당합니다. 어떤 자료형일지 모르니 반환형 데이터는 void포인터입니다. 


하지만 그냥 메모리만 할당하고 해제하지 않으면 메모리가 누출됩니다. 우리는 메모리를 이제 쓰지 않을 경우(거의 함수 반환 직전)에 free함수를 써서 메모리를 해제해야합니다.


void free(void *ptr)


이제 malloc함수와 free함수를 사용해서 위의 코드를 오류없이 실행시켜보도록 하지요.



#include <stdio.h> #include <stdlib.h> int main() { int size, i; scanf("%d", &size); int *arr=(int*)malloc(sizeof(int)*size); for (i = 0; i < size; i++) scanf("%d", &arr[i]); for (i = 0; i < size; i++) printf("arr[%d]=%d\n", i, arr[i]); free(arr); }



무리없이 실행도 되고, 원하는 결과를 얻을 수 있습니다.


malloc함수 외에도 calloc, realloc함수가 있습니다.


void *calloc(size_t n, size_t size)


calloc은 malloc과 힙영역에 할당하는 것을 똑같습니다. 사용법과 초기값이 다른데요.

calloc은 할당된 메모리를 전부 0으로 초기화합니다. malloc은 0으로 전부 초기화 시키지 않죠. 

쓰임새는 아래의 코드와 같습니다.


#include <stdio.h> #include <stdlib.h> int main() { int n, i; scanf("%d", &n); int *arr=(int*)calloc(n,sizeof(int)); printf("calloc 0으로 초기화\n"); for (i = 0; i < n; i++) printf("arr[%d]=%d ", i, arr[i]); printf("\n"); for (i = 0; i < n; i++) scanf("%d", &arr[i]); for (i = 0; i < n; i++) printf("arr[%d]=%d\n", i, arr[i]); free(arr); }





realloc함수는 할당된 메모리를 다시 할당할때 쓰입니다. 기존에 할당했던 포인터와 다시 할당할 size를 매개변수로 전달합니다. 기존에 있던 값은 변함이 없습니다.


void *realloc(void *memblock, size_t size)


realloc은 아래처럼 쓰면 됩니다.

#include <stdio.h> #include <stdlib.h> int main() { int n,m,i; printf("처음 크기 입력\n"); scanf("%d", &n); int *arr=(int*)malloc(sizeof(int)*n); for (i = 0; i < n; i++) scanf("%d", &arr[i]); for (i = 0; i < n; i++) printf("arr[%d]=%d\n", i, arr[i]); printf("다시 할당될 크기 입력\n"); scanf("%d", &m); //realloc함수도 다시 할당 arr = (int*)realloc(arr, sizeof(int)*m); for (i = n; i < m;i++) scanf("%d", &arr[i]); for (i = 0; i < m; i++) printf("arr[%d]=%d\n", i, arr[i]); free(arr); }





2차원 배열은 어떻게 할당할까요?

우선 1차원 배열의 메모리를 힙에 할당하고, 1차원배열 각각 메모리를 할당하면 됩니다. 


메모리 해제할때도 일차원배열 메모리를 해제하고, 2차원배열 메모리를 하제하면 됩니다. 조금 복잡할 수도 있어요.



#include <stdio.h>
#include <stdlib.h>

int main() {
	int n, m, i;
	int **arr;
	scanf("%d %d", &n,&m);

	arr = (int**)malloc(sizeof(int *)*n);


	for (i = 0; i<n; i++)
		arr[i] = (int *)malloc(sizeof(int)*m);

	for (int i = 0; i < n; i++)
		for (int j = 0; j < m; j++)
			scanf("%d", &arr[i][j]);
	printf("\n\n");
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < m; j++)
			printf("arr[%d][%d]=%d\n", i, j, arr[i][j]);
		printf("\n");
	}

	for (i = 0; i<n; i++)
		free(arr[i]);
	free(arr);

}
	




이제까지 메모리 할당 함수를 사용해서 동적메모리를 할당하는 방법을 알아보았습니다. 조금 어렵죠?

반응형
블로그 이미지

REAKWON

와나진짜

,

void 포인터


void 포인터??

이런 포인터는 처음 들어봤네요~ 분명 void라는 것은 함수 앞에서 반환형이 없을 때 쓰이는 키워드로 아는뎁;

void main은 많이 봤는데...


지금부터 이야기할 주제가 바로 void포인터라고 합니다. 우리는 이제껏 자료형이 정해져있는 포인터 예를 들면


int *a


라는 형태는 봐왔잖아요?


그리고 그 포인터에 주소를 할당하는 방법은 이런 것이죠.


int a=100;

int *b=&a; 


이걸 말로 풀어서 설명한다고 하면

b는 a의 주소를 가지고 있고, b를 통해 a를 참조할 수 있는데 그곳에는 int형 데이터가 있다!

라고 말이에요.


우리는 이 말속에서 힌트를 얻을 수 있습니다. 

그곳에는 int(정수형)형 데이터가 있다 라는 말을 집중해주세요.


위의 int *를 void *로만 바꾸어 써보고 읽어볼게요.


int a=100;

void *b=&a;


b는 a의 주소를 가지고 있고, b를 통해 a를 참조할 수 있는데 그곳에는 void형 데이터가 있다.




void 형 데이터가 있다....(?) void는 "빈공간"이라는 뜻을 내포하고 있는데요. 컴퓨터는 자료형을 모르기 때문에 빈공간처럼 보는 겁니다.

그러기 때문에 앞으로 우리는 이렇게 읽어야 할 겁니다.


b는 a의 주소를 가지고 있고, b를 통해 a를 참조할 수 있는데 그곳에는 알 수 없는 자료형 데이터가 있다.


이 형태 그대로 데이터를 참조하면 컴퓨터는 "아 몰랑!" 합니다.


printf("%d\n", *b);     //오류

 

얼마나 참조해야하는지 알 수 없기 때문입니다. void는 단순히 주소값만을 가지고 있습니다.


우리는 void가 가리키고 있는 데이터의 형태를 알고 있습니다. 우리는 똑똑하니까요(?). 그래서 우리는 *b가 무엇이냐 라고 질문할때 100이라고 대답할 수 있습니다. 우리는 똑똑하기 때문이죠.


하지만 단순히 주소값만!알고 있는 우리 void형은 그 형태가 int형이든 char 형이든 구조체든 문자열을 가리키고 있는 포인터이든 상관없이 단순히 주소값만이요. 그러니 void포인터는 자료형이 무엇이든 간에 주소값만 바라봅니다.


주소만 갖으면 되기 때문에 포인터의 크기(4바이트)만 갖고 있고, 포인터 연산조차 할 수 없습니다.


정말 읽을 수 없는 지 한번 코드로 살펴봅시다.

#include <stdio.h>

int main() {
	int a = 10;
	void *b = &a;
	printf("%d\n", *b);
}
	


실행시킬 수 조차없이 빨간줄로 


Error:Expression must be a pointer to a complete object type


라는 에러를 보게 됩니다. 

완전한 형태의 포인터로 바뀌어야한다 라고요.




우리는 컴퓨터에게 "너가 가리키고 있는 데이터 자료형은 int형이야" 라고 명확히 알려주어야합니다. "그러니까 넌 묻지도 따지지도 말고 4바이트만 읽으면 돼!" 라고요.

어떻게 알려줄까요??


우리는 형변환을 알고 있습니다. 그걸 사용하는 것이죠.


printf("%d\n", *(int*)b); 


이렇게 하면 void*는 int형을 읽을 수 있는 int*로 변환되게 됩니다.

정말 빨간 줄 없이 int형 데이터를 읽을 수 있는 지 코드로 볼까요?

#include <stdio.h>

int main() {
	int a = 10;
	void *b = &a;
	printf("%d\n", *(int*)b);
}


빨간 밑줄은 없어졌군요~ 





실행도 정상적으로 되는 것을 확인할 수 있고, 10을 정확히 읽는 것도 확인할 수 있습니다.


char형은 어떻게 변환할까요? 그것도 간단합니다. 바로 char*로만 바꾸어주면 됩니다.

#include <stdio.h>

int main() {
	char a= 'c';
	void *b = &a;
	printf("%c\n", *(char*)b);
}





네, 'c'라는 문자를 제대로 읽을 수 있군요.




문자열 역시 다르지 않습니다.

#include <stdio.h>

int main() {
	char *str= "문자열";
	void *b = str;
	printf("%s\n", (char*)b);
}

str자체가 문자열을 가리키고 있는 포인터이기 때문에 str변수 앞에 &(amp)를 붙여주지 않습니다. 이해하죠?




문자열도 잘 읽어오는군요.



이렇게 void포인터는 만능입니다. 어떤 자료형이건 바로 참조할 수있죠. 알맞은 자료형으로만 변경한다면 말이죠.


우리는 void포인터를 동적할당할때 유용하게 사용합니다. 동적할당에 사용하는 malloc과 같은 함수들이 void*로 반환하기 때문입니다.

malloc은 특정한 size의 크기로 메모리를 할당하고나서 우리들에게 알맞게 변환해서 써라 라는 의미로 void*를 내뱉게 됩니다.


어차피 나중에 배울 malloc함수의 원형 한번 볼까요?


void *malloc(size_t size)


앞서 말한대로 void*를 토하고 있습니다. 너네가 맘대로 바꾸라고 말이죠.


한번 보세요. 아~ 이런 변태같은 함수도 있구나~ 라고 기억하시기 바랍니다.



이것으로 void포인터에 대해 설명해보았습니다. 감사합니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

Fragment


프래그먼트(Fragment)는 안드로이드 3.0부터 지원되었다고 하는데요. 프래그먼트란 무엇일까요..?

간단하게 말하자면 여러가지 화면을 유연하게 지원하기 위한 것이라고 할 수 있겠습니다. 


어플리케이션의 단위는 Activity이고, 그 Activity도 여러 Fragment로 구성될 수가 있습니다. 


예를 들면 카카오톡의 친구목록, 채팅목록 같은 화면은 프래그먼트이고 이 두 프래그먼트는 같은 Activity에 있는 것과 같아요. 맞나? 제가 카톡 개발자가 아니어서


Activity에 Fragment가 포함되는 것이기 때문에 Fragment간의 직접적인 통신은 막고 있습니다. 반드시 Activity를 통해서 Fragment간의 통신을 해야합니다.






프래그먼트도 Activity와 같이 여러 단계의 주기를 거치게 됩니다. 그 중 눈여겨 볼 메소드는 onCreateonCreateView,그리고 onPause랍니다.




onCreate()

안드로이드 시스템은 프래그먼트가 실행될때 이 메소드를 호출하게 됩니다. 컴포넌트를 초기화하고 싶다면 이곳에서 하세요. 매개변수인 Bundle 객체를 통해서 Activity에서 데이터를 전달받을 수도 있습니다.


onCreateView()

이 메소드는 UI를 생성하는 메소드입니다. UI를 제공하지 않는다면 null을 반한 할 거구요. 대개 전달받은 LayoutInflater로 inflate메소드를 통하여 View를 반환합니다.


프래그먼트는 Activity에서 FragmentManager 객체를 통해서 관리되어 집니다.  MainActivity에서 getSupportFragmentManager메소드를 통해 객체를 생성할 수 있습니다.




프래그먼트를 사용한 어플리케이션 예제

이제 프래그먼트를 사용해서 간단한 어플을 만들어볼텐데요, 우선 Empty Activity로 프로젝트를 구성해주세요.


두 개의 프래그먼트를 구성 할 두 개의 레이아웃 파일을 만들어 주겠습니다.


fragment1.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffee11">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Fragment1"
android:textSize="30dp"/>
</RelativeLayout>

fragment2.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#22ff33">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Fragment2"
android:textSize="30dp"/>
</RelativeLayout>


두개의 레이아웃 파일은 별거 없습니다. 그냥 현재 어떤 프래그먼트인가를 표시하는 텍스트뷰를 하나 포함하고 있고, 더 구별하기 쉽도록 배경색을 다르게 지정했습니다.


우리는 이 레이아웃 파일을 프래그먼트와 연결해야합니다. 바로 UI를 구성해야하는 것이죠. 

그 메소드가 onCreateView라는 메소드라고 했습니다.


Fragment1.java


import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class Fragment1 extends Fragment {

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment1,container,false);
}
}


Fragment2.java

import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class Fragment2 extends Fragment {

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment2,container,false);
}

}


Fragment1 클래스는 fragment1 레이아웃을 UI로 삼고 있고, Fragment2 클래스는 fragment2 레이아웃을 UI로 가지고 있습니다.


이것을 View 객체로 가져오기 위해서는 LayoutInflater로 inflate메소드를 통해 가져올 수 있습니다. 


이제 여기까지 프래그먼트와 레이아웃간의 연결을 끝났습니다. 이제 프래그먼트를 포함할 activity_main.xml을 구성해야합니다.


activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="프래그먼트1"
android:textSize="25dp"
android:onClick="changeFragment"
android:id="@+id/fragment1"/>

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="프래그먼트2"
android:textSize="25dp"
android:onClick="changeFragment"
android:id="@+id/fragment2"/>

</LinearLayout>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/container"
android:orientation="vertical"/>
</LinearLayout>

id가 container인 LinearLayout이 보이시나요? 여기가 프래그먼트를 포함하는 container 역할을 하게 됩니다.


각 버튼은 changeFragment 메소드를 통해서 프래그먼트를 바꾸는 역할을 하게 됩니다.




MainActivity.java

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends AppCompatActivity {

FragmentManager fm;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Fragment fragment=new Fragment1();
fm=getSupportFragmentManager();
fm.beginTransaction().add(R.id.container,fragment).commit();
}

public void changeFragment(View view){
int id=view.getId();
Fragment fragment=null;
switch(id){
case R.id.fragment1:
fragment=new Fragment1();
break;
case R.id.fragment2:
fragment=new Fragment2();
break;
}

fm.beginTransaction().replace(R.id.container,fragment).commit();
}
}

Activity는 FragmentManager를 통해서 Fragment를 관리하게 됩니다. FragmentManager를 통해서 FragmentTransaction객체를 가져옵니다. 


FragmentTransaction객체는 프래그먼트를 추가, 교환, 제거할 수 있습니다. replace메소드로 container의 프래그먼트를 교체하고 있는 걸 확인할 수 있습니다. 마지막에는 반드시 commit메소드를 호출해야 replace가 반영됩니다.




결과


프래그먼트1을 버튼을 누른 경우(또는 앱이 실행된 첫 화면)




프래그먼트2를 버튼을 누른 경우




프래그먼트를 사용해서 어플리케이션을 만들어보았습니다. 처음에만 조금 헷갈리지 나중에는 익숙해져... 요... 

반응형
블로그 이미지

REAKWON

와나진짜

,