구초제(Structure)

 

우리는 같은 자료형을 여러개 쓸 때는 배열이라는 것을 썼었죠. 아주 유용합니다. 배열과 반복문을 통해서 조금 더 쉽게 프로그래밍을 할 수 있었습니다.

 

하지만 같은 자료형이 아닌, 다른 자료형을 하나로 통합해서 관리해야할때는 어떻게 할까요??

 

통합적이게 데이터를 묶고 쉽게 접근할 수 있는 기법이 바로 구조체라고 합니다.

 

만약 구조체가 없이 사람들의 데이터를 관리한다고 칩시다.

 

char name[MAX_N][30];

int age[MAX_N];

char phone[MAX_N][30];

 

자료형이 서로 다르니, 서로 같아도 쓰임새가 다르니, 여러개의 변수를 관리할 수 밖에 없습니다. 프로그래밍의 가독성과 효율성을 떨어뜨리게 되는 것이죠.

 

 

하지만 구조체는 우리의 작은 소원을 들어줍니다. 하나로 묶는 것이죠. 

구조체를 정의하는 방법은 상당히 간단합니다. 이렇게요.

 

struct User{

char name[30];

int age;

char phone[30];

}

 

User가 바로 구조체의 이름이며, 그 안의 변수들은 멤버변수라고 합니다.

 

이렇게 데이터를 묶어서 배열로 정의하게 되면 저 위의 코드를 조금 더 편하게 관리하고 접근할 수 있습니다.

 

struct User user[MAX_N];

 

대충 이런 느낌입니다. 쉽죠?

 

그럼 오늘 포스팅을 이걸로 마치겠습니다.

 

이제 본격적으로 구조체를 어떻게 사용하는 지 알아보도록 하겠습니다.

구조체 정의는 위에서 본것과 같이 함수밖에서(전역) 구조체 안의 멤버 변수들을 선언해줍니다. 

struct 구조체이름{

...

변수

...

};

 

그리고 함수안에서는 다음과 같이 사용할 수 있습니다.

 

struct 구조체이름 구조체변수명={ ..., 멤버 변수 값, ...};

 

이렇게 한꺼번에 구조체에 선언된 변수 순서대로 값을 초기화 할 수도 있고

또는

 

struct 구조체이름 구조체변수명;

구조체변수명.멤버변수명=변수값

...

 

하나하나 명시적으로 변수에 값을 할당할 수 있습니다.

 

변수에 접근하려면 어떻게 할까요??

그것도 역시 간단합니다. 구조체변수에 .(dot)을 찍어서 멤버변수명을 입력해서 접근할 수 있습니다.

 

구조체변수명.멤버변수명

 

 

이제까지 설명했던 내용을 코드로 간단하게 사용해보도록 합시다.

#include <stdio.h>
struct User {
        char name[30];
        int age;
        char phone[30];
};
int main() {
        struct User user = { "REAKWON",2018,"010-????-????" };
        printf("이름:%s\n", user.name);
        printf("나이:%d\n", user.age);
        printf("전화번호:%s\n", user.phone);
}

 

원하는 대로 결과가 나오죠?

 

그런데 우리는 구조체를 typedef로 조금 더 간단하게 선언할 수 있습니다. 

 

typedef은 자료형을 조금 더 편하게 관리하기 위해서 쓰입니다.

typedef 원래의 자료형 내가 정의한 자료형;

이렇게 하면 변수를 원하는 대로 정의해줄 수 있습니다.

아래와 같이요.

typedef unsigned int u_int;u_int val=30;

 

typedef struct 구조체명{

구조체 정의부

} 정의할 구조체명;

 

그러면 선언에서 struct를 빼고

 

정의한 구조체명 구조체변수

 

이렇게 조금 더 편하게 쓸 수도 있습니다.

 

아래의 코드는 바로 위의 코드를 typedef를 사용해서 바꾼 코드랍니다.

 

 

#include <stdio.h>
typedef struct User {
        char name[30];
        int age;
        char phone[30];
} _User;
int main() {
        _User user = { "REAKWON",2018,"010-????-????" };
        printf("이름:%s\n", user.name);
        printf("나이:%d\n", user.age);
        printf("전화번호:%s\n", user.phone);
}

 

구조체 크기

구조체의 크기를 한번 알아보도록 할까요?

아까와 같은 구조체를 한번 변경해서 보도록하겠습니다.

struct User { 
    char name[10]; 
    int age; 
    char phone[10]; 
}

char자료형은 1바이트이고 10개의 배열을 가졌으니, 10바이트

int자료형은 4바이트입니다. 

또 char배열 10개로 10바이트해서 구조체의 크기는 총 24바이트겠네요.

 

코드로 확인해보도록 하지요.

 

#include <stdio.h>

struct User {
    char name[10];
    int age;
    char phone[10];
};

int main() {
    printf("User 구조체 사이즈:%d\n",sizeof(struct User));
}

 

 

 

 

??????

제가 틀렸군요. 28바이트가 나오네요.

 

왜일까요??

구조체의 사이즈는 그 구조체 멤버중에서 가장 큰 자료형에 따라 결정이 됩니다. 가장 큰 자료형은 int로 4바이트가 되죠? 그래서 4의 배수로 크기가 결정됩니다.

이해를 돕기 위해 그림을 첨부합니다. 

 

 

사각형 한 칸의 크기는 1바이트를 나타냅니다. name과 phone은 10바이트를 차지할 것 같지만, char형은 1바이트이고, int형인 age는 4바이트니까 큰 자료형에 의해서 4의 배수로 메모리가 할당이 됩니다.

name과 같이 12바이트를 맞추기 위해 여분의 바이트를 붙여주는 것을 우리는 패딩(padding)이라고 합니다.

 

아~ 그래서 28바이트가 나오는 구나. 

그렇다면 메모리가 낭비될텐데 조금 더 아낄수 있는 방법은 없을까요?

 

우리는 변수명을 단지 바꾸어 주어 이런 메모리 낭비를 막을 수 있습니다. 이렇게요.

 

#include <stdio.h>

struct User {
    char name[10];
    char phone[10];
    int age;
};

int main() {
    printf("User 구조체 사이즈:%d\n",sizeof(struct User));
}

 

이때의 메모리를 보면 아래의 그림과 같습니다.

 

이렇게 4바이트를 아낄 수 있지요.

 

왜 이렇게 메모리를 큰 자료형에 맞출까요?? 네트워크 통신에서 관련이 있습니다. 우리는 아직 그 크기에 그렇게 신경써도 되지 않아도 됩니다.

 

이렇게 구조체가 무엇인지, 어떻게 정의하는지 알아보았는데요. 다음 시간에는 구조체에 대해서 조금 더 세세하게 알아보고 구조체 포인터에 대해서도 이야기해보도록 하겠습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

동적 메모리 할당


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


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

와나진짜

,

함수포인터

우리는 이제까지 어떤 변수를 가리키는 포인터, 배열을 가리키는 포인터를 사용해보았어요. 물론 이정도를 알아도 상당히 훌륭하다고 생각합니다.

그렇지만 변수뿐만 아니라 함수도 포인터로 가리킬 수 있다는 것을 알게 된다면 조금 더 간지나는 프로그래밍을 시도해 볼 수 있겠습니다. 이제부터 설명할 것이 무엇이냐, 그 이름하여 함수포인터랍니다. 

함수포인터라... 그냥 포인터도 극혐인데 말이죠

함수포인터란 함수를 가리킬 수 있는 포인터를 의미합니다. 아니, 근데 그러면 주소를 알아야하는데, 함수에도 주소가 있나? 네, 있습니다. 함수가 주소를 가지고 있다구요? 제말이 구라가 아님을 보여드리겠습니다.

#include <stdio.h>
int sum(int a, int b) { 
        return a + b;
} 

int main() { 
        printf("함수 sum의 주소 : %p\n", &sum);
}


이 코드의 결과는 어떻게 될까요? 우리는 변수명 앞에 &(amp)를 붙여 변수의 주소를 알게 됩니다. 그렇다면 &함수명은 무엇을 의미하게 될까요? 아무리 눈치없어도 진짜 이정도면 눈치채야지~

신기하네요. 16진수로 어떤 수가 나오네요. 눈치채셨겠지만 함수의 주소를 의미하게 됩니다. 바늘가는데 실간다는데 주소가 있으면 포인터가 있겠죠. 그렇다면 함수포인터를 선언하는 방법을 알아보도록 하겠습니다. 간단합니다.

①int ②(*ptrSum)③(int a,int b)

 

일반 포인터와 마찬가지로 주소를 가리킬때는 *을 사용해서 포인터라고 알려줍니다.

①은 함수의 반환형을 의미합니다.

②는 함수포인터의 이름을 의미합니다. (변수명과 같이 임의로 정해줍니다.)

③은 매개변수를 의미합니다. 매개변수가 없을 때는 빈 괄호나 void를 사용합니다.

 

네, 위 세가지만 지켜주면 됩니다.

그러니까 ptrSum이라는 함수포인터는 반환형이 int형이고 매개변수 2개를 갖는데, 둘 다 int형 매개변수인 함수포인터가 되겠습니다.

그렇다면 다음과 같은 함수포인터는 무엇을 의미할까요?

void (*ptrFunc)()

함수명이 ptrFunc인데, 반환값이 없고(void), 매개변수도 없는 함수포인터를 의미합니다. 이제 응용가능하시겠죠? 함수포인터에서 반환형과 매개변수가 일치하는 함수만 함수포인터에 할당이 가능합니다. 기억하세요.

함수포인터를 선언하는 방법은 알게 되었으니, 다음 코드를 통해서 함수포인터의 값이 바로 그 함수의 주소인지 확인도 해보고, 사용도 해봅시다.

#include <stdio.h>
int sum(int a, int b) { 
        return a + b;
}

int main() {
        int(*ptrSum)(int a,int b); 
        ptrSum = sum; 
        printf("sum의 주소: %p\n", &sum); //&sum은 sum과 같음 
        printf("ptrSum의 값: %p\n", ptrSum);
        printf("ptrSum의 주소: %p\n", &ptrSum);
        printf("ptrSum(%d, %d) = %d\n", 3, 4, ptrSum(3, 4));
}

 

우리는 sum이라는 함수의 주소가 0x00081023번지라는 것과 ptrSum이 0x00081023을 가리키는 것을 확인할 수 있습니다(실제 sum도 함수의 주소, &sum도 함수의 주소입니다).

그리고 그 함수포인터의 주소는 0x0026FB04번지네요.

함수포인터를 통해서 호출(ptrSum(3,4)) 확인해볼 수 있군요. 

(코드에서 나오지는 않았지만 함수포인터의 크기는 역시 4바이트입니다. 포인터이기 때문에요.)

조금 더 쉽게 그림을 통해서 보도록할게요.

 

포인터를 설명할때 아주 많이 사용하는 그림이죠? 지겹죠?

0x0026FB04번지에 있는 ptrSum이라는 함수포인터는 0x00081023번지에 있는 함수 sum의 시작주소를 가리키게 됩니다.

sum은 함수이기 때문에 시작주소를 갖고 있고, 그 주소를 기점으로 매개변수를 할당, 그리고 함수 내부의 변수들을 스택에 따라 쌓아올립니다. 그러니까 함수의 시작주소를 통해 변수의 주소를 알게 됩니다. 함수의 시작주소는 중요하단 거죠. (더 자세하게 함수호출과정을 아는 것도 도움이 됩니다. 구글형님께 물어보세요.)

그런 sum의 시작주소를 ptrSum은 알고 있기 때문에 sum함수를 호출할 수 있는 겁니다.

아니, 그렇다면 sum함수만 호출하면 되지, 왜 굳이 포인터를 써서 호출하는 거야?

저도 무척이나 이런 생각을 하고 하고 또 하면서 그냥 함수포인터를 흘려 넘겼었죠.

근데 쓰임새는 꽤나 많습니다. 예를 들면 함수 자체를 매개변수로 받고 싶을때가 있을 겁니다.

프로젝트를 진행할때 한 사람만이 진행한다면 물론 상관이 없지만, 여러 사람과 협업을 해야하거나, 라이브러리를 제공할때, 함수에 함수자체를 매개변수로 받아야할 때가 있습니다. 누가 어떤 함수를 필요로 할지 모르니, 어떤 형식으로 함수를 정의해서 매개변수로 전달하게 되면 그 함수를 내부에서는 호출하게 되는 식으로 말이죠.

void func(void (*ptr)()) {
...      ptr();       ... 
}

그리고 또 구조체에서 멤버함수를 커스터마이징할때도 사용할 수가 있습니다. 

struct animal{     
...       void (*walk)();       ... 
}

 

위에서 walk라는 멤버함수를 우리가 원하는 대로 기능을 정의해줄 수 있습니다. 물론, 함수포인터의 형식과 맞는다면 말이죠. 이런 방식은 자바에서 interface라고 하여 메소드를 구현하는 방식과 비슷해보입니다. 함수포인터를 더 연습해보고 개념을 확실히 이해하세요.

이것으로 함수포인터에 대해 알아보았습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

문자열과 char 포인터

오늘은 심심한데 문자열에 대해서 이야기 해볼까 해요~ 문자열과 포인터는 C언어에서 너무나 귀찮은 놈들인데,,, 그래도 꼭 쓰이니까요. char 자료형은 문자를 변수로 갖는 건 모두 아는 사실이죠?

근데~ 우리는 문자열을 쉽게 할당하고 싶단 말이에요. 우리는 배열이라는 아주 편리한 변수 선언 법을 알고 있답니다.

배열로 문자열을 표현하는 방법을 알아보겠습니다.

char hello[6] = { 'h','e','l','l','o','\0' };

이 표현은 char이 자료형을 배열로 문자열을 표현한 방법이랍니다. '\0' 라는 문자는 NULL문자라는 뜻입니다. 문자열의 끝을 알려줍니다.

그래서 hello가 다섯글자임에도 불구하고 배열 크기를 6으로 잡은 겁니다.

또 이런 선언법도 가능합니다.

char hello[6] = "hello";

그렇다면 우리가 문자열의 길이를 알고 싶다면 널문자가 나타나기 전까지만 세어주면 문자열의 길이를 알 수 있겠네요.

#include <stdio.h>

int main() {
        char *ptr = "ABCDEF";
        int len = -1;
       
        while (*(ptr+(++len)));
        printf("문자열 길이: %d\n", len);
}

그 결과는 이렇겠네요.

 

len=-1인 이유는 null문자 이전까지만 세어주기 위함입니다. while의 조건절은 null이면 멈추어 버립니다. 여기까지는 쉽네요. 포인터로는 어떻게 표현할까요?

사실 문자열("~~~~")은 그 문자열이 시작되는 주소를 가리키게 됩니다. 주소를 가리킨다!?  그러면 포인터가 생각나지 않나요?

왜냐면 주소를 포인터로 가리키면 문자열을 찾을 수 있으니까요.

그러면 이렇게 선언할 수 있을까요?

char *ptr = "hello";

포인터 ptr은 "hello"라는 문자열을 가리키는 포인터입니다. 

그림에서 보는 것과 같이 ptr은 문자열 "hello"의 주소를 가리키고 있고, 그렇기 때문에 참조가 가능한 상태가 됩니다. 그렇다면 어떤 포인터 역시 hello를 가리킨다면 그 주소는 같을까요?

코드와 결과로 확인해보도록 합시다.

 

#include <stdio.h>  int main() {   	char *ptr1 = "hello"; 	char *ptr2 = "hello"; 	printf("%s, %s\n", ptr1, ptr2); 	printf("%p, %p\n", ptr1, ptr2); } #include <stdio.h> 

int main() {   
        char *ptr1 = "hello"; 
        char *ptr2 = "hello"; 

        printf("%s, %s\n", ptr1, ptr2);
        printf("%p, %p\n", ptr1, ptr2);
}

 

같다는 것을 알 수 있습니다. 우리는 이런 그림을 그려볼 수 있겠네요.

ptr1과 ptr2는 서로 같은 문자열을 가리킵니다.  배열과 포인터에 대해서 선언방법은 그렇게 차이가 없어보이죠?

그렇다면 문자열 배열과 포인터는 서로 같은 성질을 갖고 있을까요?

만약 아래와 같은 코드를 입력한다 arr에 ptr가 가리키는 문자열을 넣으라는 거겠죠??

char arr[10] = "world";

char *ptr = "hello";

arr = ptr;

 "hello"라는 문자열의 길이는 배열크기보다 작기 때문에 들어갈 것입니다. 이렇게 생각하셨다면 다시 생각해봅시다. 오류나니까요.

문자열 "hello" 그 주소 자체를 반환합니다. 그러니까 "hello"의 시작주소가 되는 것이죠. 그것이 ptr이 갖고 있는 값입니다.

arr자체는 arr[0]의 주소, 즉 배열의 시작 위치를 말합니다. 이러한 시작 주소를 마음대로 ptr이 가리키고 있는 주소로 바꿀 수 없습니다.

이 의미는 더 쉽게 풀어서 이야기하면

int a = 0;

int b = 30;

&a = b;

랑 유사한 짓거리를 하는 것이라는 거죠. 마치 a의 주소를 b의 값으로 변경하라는 것과 유사하게 되어버립니다.

하지만 그 반대는 가능합니다. 이렇게요.

char arr[10] = "world" ;

char *ptr = "hello";

ptr = arr;

ptr은 주소를 갖을 수 있는 포인터, arr은 arr[0]의 주소! 말이 되죠 이건??

그러니 ptr은 arr과 동일한 곳을 가리키게 되는 겁니다.

만약

arr = ptr;

이걸 죽어도 써야겠다. 난 arr에다가 ptr의 문자열을 진짜 안쓰면 디질거 같다.  하시는 분들은 ptr의 문자열을 복사해서 쓰는 방법밖에 없습니다.

strcpy(arr, ptr)

이렇게 하시면 arr에 ptr이 가리키는 문자열을 그대로 복사해서 arr에 쑤셔 넣습니다. 주의 할 사항은 arr의 크기는 ptr이 가리키고 있는 문자열의 길이 이상으로 커야한다는 겁니다.

그렇지 않으면 런타임 오류납니다. 컴파일에서 문자열의 길이를 검사하지 않습니다!

이 오류가 바로 그 유명한 버퍼오버플로우(buffer overflow)가 됩니다. 취약점인거죠. 버퍼오버플로우를 통해 해커는 함수의 return 주소를 변경하여 자신의 실행코드를 실행합니다. 별짓을 다할 수가 있게 됩니다그래서 그 대안으로 strncpy, strncat 이런것이 나오게 된겁니다.

그리고 또!

포인터와 배열에는 다른 차이점이 있습니다.

문자열을 초기화 할때 배열은 배열 원소를 변경할 수 있지만, 포인터는 배열의 원소를 바꿀 수 없습니다. 즉, 포인터로 초기화 한다면 상수적인 성격을 띈다라는 것입니다.

가령, 아래 코드가 있다면 ptr[0] 변경시 오류가 발생합니다.

char hello[10] = "hello";

char *ptr = "hello";

ptr[0] = 'H';  //오류

hello[0] = 'H';

하지만

char hello[10] = "hello";

char *ptr = hello; 

ptr[0] = 'H'; 

hello[0] = 'H';

이건 오류가 나지 않습니다. 왜냐면 hello는 배열이거든요. 배열은 그 원소의 값이 변경가능합니다. ptr은 배열의 시작주소를 참조하고 있는 포인터이기 때문입니다.

 

그래서 이러한 strcat를 써먹을때도

#include <stdio.h>
#include <string.h>  

int main() {
        char *hello = "hello, ";  
        strcat(hello, "world");
        printf("%s\n", hello);
}

가 아닌

#include <stdio.h>
#include <string.h>

int main() { 
        char hello[20] = "hello, ";  
        strcat(hello, "world");
        printf("%s\n", hello);
}

 

이런 형태나

#include <stdio.h>
#include <string.h>  

int main() {  
        char hello[20] = "hello, ";
        char *ptr = hello;  
        strcat(ptr, "world");  
        printf("%s\n", ptr);
}

이런식으로 쓰여야 한다는 겁니다. 이렇게 간단하게 문자열과 포인터에 대해서 알아보았습니다. 부족한 점은 나중에 또 보충 설명해보도록 할게요 ㅎㅎ

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

배열과 포인터

 

배열과 포인터는 아주 유사한 성격을 갖고 있습니다. 배열은 뭘까요?

 

우선 배열에 대해서 알아봅시다. 배열은 같은 자료형이 연속된 공간으로 나열되있는 것을 뜻합니다. 배열에 대해서 조금 더 쉽게 접근하기 위해서 index를 갖는데요. index를 통해 빠르게 배열 원소에 접근하게 됩니다.

 

int a[5]={5, 3, 1, 2, 4};

 

이와 같은 배열이 있다면 메모리의 구조는 바로 이렇게 구성될겁니다.

 

 

왜 이런식으로 배열이 구성이 될까요??

 

왜 배열의 원소는 4바이트나 되는 걸까요.

그 이유는 int가 4바이트이기 때문입니다. 따라서 4바이트씩 주소가 증가하고 있는 겁니다.

그렇다면 char형의 배열은 한 원소의 크기가 1바이트이고, long long배열의 한 원소 크기는 8바이트가 되는 겁니다. got it?

 

 

사실 a라는 변수는 a[0]의 주소값을 갖고 있습니다. 배열의 시작주소를 가지고 있다는 거지요.

 

따라서 a는 &a[0]와 같은 값을 나타냅니다. a는 a[0]의 주소를 가리키고 있다는 겁니다. 

a가 주소값을 가지고 있다고??

그렇다면 a가 가지고 있는 주소를 참조한다면 *a로 a가 참조하는 값을 구할 수 있겠네요. 그러면 정말 a[0]의 값을 참조할까요? 

코드로 확인해보세요.

 

 

이제부터는 다른 배열의 다른 연산법을 알아보겠습니다. a는 a[0]을 가리키고 있는 포인터라고 했습니다. a[0]은 5의 값을 가지고 있습니다. 

헌데, a가 a[0]를 가리키고 있으니, *a는 a[0]의 값을 나타내게 되는 것이지요.

 

그렇다면 이렇게 변형해볼까요?

*(a+0) 는 *a와 같은 뜻이 될까요? 아마 그렇겠죠?

그렇다면 *(a+1)은 어떤 값을 나타나게 될까요?? 결론부터 말씀드리면 a[1]의 값을 갖게 됩니다. 어떤 포인터의 +1은 그것이 참조하고 있는 자료형의 크기만큼 주소를 더하라는 의미가 됩니다. 

 

그렇다면 *(a+i)는 a[i]가 되겠네요!! 와우!!

 

이제껏 지껄인 말이 확실한지 코드로 확인해보도록 하겠습니다.

 

#include <stdio.h>

int main() {
	int i;
	int a[5] = { 5,3,1,2,4 };
	
	
	
	printf("a의 주소 &a[0] = %p, a = %p \n", &a[0],a);
	printf("a의 값: %d\n", *a);
	for (i = 0; i < 5; i++)
		printf("\t 주소 %p  a[%d] : %d, *(a+%d) : %d\n",(a+i),i,a[i],i,*(a+i));

}

 

 

다음 사진은 결과를 보여줍니다.

 

 

 

제가 설명한 내용이 맞나요??

a의 값은 a[0]의 주소와 같네요!! 주소 값은 역시 4바이트씩 증가합니다. 

아까 포인터 연산 역시 제가 설명한 대로 나오고 있습니다.

포인터로 배열을 참조할 수 있습니다.

 

방금 전 배열 a를 포인터 ptr로 참조해보도록 하지요.

 

int *ptr = a;

 

로 간단하게 a라는 배열을 포인터로 참조할 수 있습니다.

a 역시 배열의 주소를 가지고 있고 ptr 역시 배열 a를 가리키고 있습니다. 그래서 a와 ptr은 같은 곳을 바라보게 되는 거죠.

 

 

그러면 ptr 역시 a가 가지는 특성을 그대로 가지고 있을까요?

코드로 확인해봅시다. 컴퓨터는 거짓말을 하지 않으니까

 

 

#include <stdio.h>

int main() {
	int i;
	int a[5] = { 5,3,1,2,4 };
	int *ptr = a;
	
	
	
	printf("a의 주소 &a[0] = %p, a = %p \n", &a[0],a);
	printf("a의 값: %d\n", *a);
	
	for (i = 0; i < 5; i++)
		printf("\t 주소 %p  a[%d] : %d, *(a+%d) : %d\n",(a+i),i,a[i],i,*(a+i));

	printf("\n");

	printf("ptr이 가리키는 주소 : %p\n", ptr);
	for (i = 0; i < 5; i++)
		printf("\t 주소 %p  p[%d] : %d, *(p+%d) : %d\n"
			,(ptr+i),i,ptr[i],i,*(ptr+i));
	
	
}

그 결과입니다.
 

 

정확히 똑같은 성질을 갖고 있다는 것을 알 수 있습니다.

 

그러면 둘의 차이는 없을까요??

 

이 둘의 차이는 크기에서 차이가 납니다.

sizeof연산을 한 번 해보도록 하지요.

 

 

#include <stdio.h>
int main() {
	int i;
	int a[5] = { 5,3,1,2,4 };
	int *ptr = a;

	printf("a의 사이즈 :%d, ptr의 사이즈 :%d\n", sizeof(a), sizeof(ptr));
}

 

결과

 

음.....

a의 사이즈는 20이고, ptr의 사이즈는 4바이트네요.

a는 배열의 크기를 갖고 있습니다.

a는 int형(4바이트) 배열로 5개가 할당되어있으니 20바이트이지요.

 

하지만 ptr은 단순히 그 배열을 참조해야하기 때문에 주소값만 저장할 수 있는 공간이면 충분합니다. 따라서 4바이트면 충분합니다.

 

그리고 배열은 상수화가 된다는 것이 특징입니다. 배열의 원소는 변경가능합니다. 하지만 배열 변수를 다른 값으로 변경할 수는 없습니다.

 

int a[5];

int b[5] = {5, 3, 1, 2, 4} ;

 

a=b;

 

a는 b와 크기가 같고 자료형이 같음에도 불구하고 이와 같은 코드는 컴파일 에러를 불러일으키게 되는 겁니다.

 

a는 a[0]의 주소입니다. b 역시 b[0]의 주소입니다.

주소는 read-only입니다. 즉, 값을 대입할 수가 없습니다 절대로!

 

 

위의 코드는 이것과 같은 얘기가 됩니다. 

&a[0] = &b[0] 이라는 거죠.

이제까지 이야기한 내용을 이해하셨다고 한다면 이해가 갈겁니다.

 

주소는 포인터만이 참조할 수 있는 포인터만의 특성입니다.

 

그러니까 위의 코드는 이렇게 바뀌어야합니다.

 

int *a;

int b[5] = {5, 3, 1, 2, 4} ;

 

a=b; //a=&b[0]과 같음.

 

배열과 포인터의 관계 이해하셨는지요??

이것으로 배열과 포인터의 관계를 마치겠습니다~~~

반응형
블로그 이미지

REAKWON

와나진짜

,

C언어 포인터란?  포인터라쓰고 탈모라 읽는다

C언어에서 포인터는 프로그래밍을 하면서 아주 어렵게 배우는 주제이기도 합니다. 어렵다기보다는 헷갈리는 경우가 많아서 멘탈붕괴가 오고는 하죠. 포인터는 말 그대로 무엇을 가리키는 놈입니다. 무엇을 가리킬까요??

다른 변수의 주소를 가리킵니다! 

모든 변수는 그 데이터가 저장되는 공간의 주소를 갖고 있습니다.

그것을 어떻게 표현할까요??

int B = 4;

int *A = &B;

이렇게 표현하게 됩니다. A는 B의 주소를 값으로 갖고 있다는 의미랍니다. 만약 B의 주소가 0x20이고 B의 값은 4라고 할때 A의 값은 0x20이 됩니다.

만약 B가 가지고 있는 값을 가져오고 싶다고 할때는 *A로 값을 참조할 수 있습니다.

그림으로 나타내면 이런 식으로 나타낼 수 가 있겠지요.

 

정말 제 말대로 되는 지 코드로 살펴보도록 하지요.

#include <stdio.h> 
int main() { 
        int B = 4;   
        int *A = &B;    
        printf("B의 값:%d\n", B);
        printf("B의 주소 :%p\n", &B);   
        printf("A의 값:%p\n", A);       
        printf("A가 참조하는 값 :%d\n", *A);     
}

 

네, 그렇게 나오네요
 
그렇다면 이중포인터는 무엇을 말할까요??
똑똑하신 여러분은 이미 알고 계시겠지만 포인터를 2번 쓰는 것을 말합니다.
아래 그림과 같은 상황이 바로 이중 포인터라는 것이죠, 포인터의 포인터! 스트레스의 향연

A는 B를 가리키고 있고, B는 C를 가리키고 있습니다.

int C = 10;

int *B = &C;

int **A = &B;

이렇게 쓸 수 있다는 거지요. A에 **(포인터의 포인터, 이중포인터)가 붙어있는 것을 확인 할 수 있네요

A는 B의 주소값을 값으로 가지고 있고, B는 C의 주소값을 값으로 갖고 있습니다. 그렇다면 A가 B의 값을 참조하려고 한다면 *A가 되겠죠?? 그러면 무슨 값이 나올까요??

바로 B가 가지고 있는 값, C의 주소입니다. A가 C의 값을 참조하기 위해서는 한 번 더 가리켜야하는데요.

그때 더블포인터가 사용이 되는 것입니다.  **A는 10을 확인할 수 있을 겁니다.

코드로 확인해 보도록 하죠.

#include <stdio.h>

int main() { 
        int C = 10; 
        int *B = &C;
        int **A = &B;

        printf("C의 값 : %d\n", C); 
        printf("C의 주소 : %p\n", &C); 
        printf("B의 값 : %p\n", B); 
        printf("B의 주소 : %p\n", &B); 
        printf("B가 참조하는 값 : %d\n", *B);
        printf("A의 값 : %p\n", A); 
        printf("A가 참조하는 값 : %p\n", *A); 
        printf("A가 참조하는 값이 참조하는 값 : %d\n", **A);
}

결과는 아래와 같습니다.

 

 

앞서 말한 대로 C의 주소를 B가 값으로 가지고 있고, B의 주소를 A가 값으로 가지고 있는 걸 확인할 수 있겠죠?? *A는 C의 주소값을 갖고 있습니다. 왜냐면 *A는 B의 값을 가리키고 있는 데 B의 값은 C의 주소이니까요!

그렇다면 삼중포인터는 어떻게 될까요??

뭐하러 어려운 포인터를 쓰나?

포인터는 언제 써먹을 수 있을까요? 대표적으로 다음과 같은 상황일때 사용합니다. 

어떤 함수가 있다고 칠게요. 아주 단순합니다. 그저 매개변수인 a와 b의 값을 교환하는 swap이라는 함수입니다.

void swap(int a, int b) {
        int temp; 
        temp = a; 
        a = b;
        b = temp;
}  

int main() {
        int x = 10;
        int y = 20; 
        swap(x, y); 
        printf("x: %d, y: %d\n", x, y); 
}

 

call-by-value

우리는 x와 y의 값을 바꾸고 싶다 이겁니다. 그 과정을 살펴보지요.

1. 우선 temp에 a의 값을 넣습니다. 그렇다면 10이 temp의 값에 들어있겠네요.

2. 그리고 b의 값을 a에 집어 넣습니다. 그렇다면 a의 값은 b의 값인 20이 들어가겠군요.

3. 마지막으로 변수 b에 temp값을 넣습니다. temp는 10이었으니까 b는 10이 되어지겠군요.

이제 함수에서 빠져나옵니다. 그 값이 바뀌어있을까요? 아닙니다. 이 함수는 a와 b를 함수 내부에서만 교체할 뿐이지, 함수 호출이 끝나고 반환되서도 x와 y의 값은 변함이 없습니다. 왜 그럴까요?

함수인자 a와 b는 전달받은 매개변수의 값(x, y)만 복사해오기 때문입니다.

예를 들면 졸업증명서 원본이 있고, 그걸 복사해서 사본을 갖고 있습니다. 사본을 불에 태워도 원본이 같이 탈까요? 동시에 같이 태우지 않는 한 원본은 살아있습니다. 이렇기 때문에 main에서 swap을 호출하고 나서도 x와 y의 값은 변함이 없습니다.

이런 함수 호출 방법을 Call-By-Value라고 부르는 것입니다.

 

call-by-reference

그렇다면 우리는 어떻게 함수를 변경해야 할까요?

그럴때 포인터가 사용이 됩니다.

void swap(int *a, int *b) { 
        int temp; 
        temp = *a; 
        *a = *b; 
        *b = temp;
}

int main() { 
        int x = 10; 
        int y = 20; 

        swap(&x, &y);
        printf("x: %d, y: %d\n", x,y);
}

a는 x의 주소를 가리키고 있고, b는 y의 주소를 가리키고 있습니다. a는 x의 주소를 참조하고 있기 때문에 x에 영향이 주게 되는 겁니다. b 역시 마찬가지가 되겠구요.

그림을 통해서 설명해보도록 하지요.

1.  temp = *a

temp에 a가 가리키는 값을 대입합니다. a가 가리키는 값은 10입니다.

 

2.  *a = *b

a가 가리키는 값에 b가 가리키는 값을 넣습니다. 그림과 같이 x의 값이 변경됩니다.

3. *b = temp

b가 가리키는 값에 temp의 값을 저장합니다. temp는 방금전 10이었기 때문에 b가 가리키는 곳(y)의 값은 10으로 변경됩니다.

 

결과는 어떨까요?

결과 역시 우리가 예상했던 대로군요. 우리는 이와 같이 함수에서의 조작이 외부 변수에 조작이 가해질때, 그럴때를 Call-By-Reference라고 합니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

C언어 문자열 처리 함수

문자열 처리는 어느 언어에서나 중요하죠.

우선 C언어에서 문자열을 처리하려면 string.h를 반드시 포함해야합니다. 

 

※이제부터 설명하는 함수들은 보안적인 취약점이 발견되있는 함수들이 있습니다. 테스트를 해보시기 전에 SDL을 NO로 설정하세요.

Project - [Project Name] Properties - (왼쪽) C/C++ - SDL checks : No
또는 전처리 구문을 사용합니다.

#define _SECURE_CRT_NO_WARNINGS

 

가장 많이 쓰이는 4개의 함수에 대해서만 우선 알아 보도록 합시다.

 

 문자열 길이  size_t strlen(const char *str) 

문자열을 input으로 넣어주면 반환되는 문자열의 길이가 나오게 됩니다. NULL문자

까지가 아닌 순수 문자열의 길이를 반환해주게 됩니다.

 

ex)

char str[20] = "hello, world";

int len = strlen(str);

 

문자열 연결 char* strcat(char *_Destination, const char* _Source)

문자열을 합치게 됩니다. _Destination 뒤에 _Source를 이어주게 됩니다. 주의해야 할 점은 매개변수로 _Destination은 배열로써 그 크기가 지정되어진 문자열이어야 합니다. 

ex) 

char dst[30]="dst";    //char *dst="dst"; 로 바꾸게 되면 error가 나오게 됩니다.

char src[30]="src";

printf("%s \n", strcat(dst,src));

 

문자열 비교 int strcmp(const char *_Str1, const char *_Str2)

문자열을 비교하게 됩니다.  

_Str1이 _Str2보다 사전순으로 나중에 등장하면 1

_Str1이 _Str2보다 사전순으로 먼저 등장하면 -1

_Str1과 _Str2와 사전순이 같다면 0

 

보통 문자열을 비교할때 이 함수를 사용하는데 두 문자열이 같다면 0이 나오기 때문에 문자열이 같은 지 if문에서 확인하려면 !strcmp(str1,str2)로 확인해야 합니다. 왜냐면 str1,str2가 같다면 0(FALSE)가 반환되기 때문이죠.

 

문자열 복사 char* strcpy(char *_Dest, const char *_Source)

문자열 _Source를 _Dest에 복사합니다. strcat와 마찬가지로 _Dest는 배열의 형태로 넘겨받습니다. _Dest에 _Source문자열을 합치기 때문에 _Dest는 _Source의 문자열을 포함할 만큼 크기가 커야합니다.

 

ex)

char _dest[20] = "hello,";

char _src[10] = "world";

strcat(_dest, _src);

 

 

 

 

위 네 가지 함수를 실제로 적용시켜볼까요??

#include<stdio.h>
#include<string.h>

int main() {

	char country[32] = "korea";
	char south[32] = "south";
	char southkorea[32] = "southkorea";
	char south_korea[32] = "South Korea";

	printf("문자열의 길이 : %d\n", strlen(country));

	strcat(south, country);
	printf("문자열 결합 : %s\n", south);

	printf("문자열 비교 : ");
	if (!strcmp(south, southkorea)) {
		printf("%s = %s\n", south, southkorea);
	}
	else {
		printf("%s != %s\n", south, southkorea);
	}


	strcpy(southkorea, south_korea);
	printf("문자열 복사 : %s\n", southkorea);

}

 

그리고 그 결과입니다.

 

이상으로 문자열과 관련해서 자주쓰이는 함수 몇가지를 살펴보았습니다.

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,