본문 바로가기

STUDY/etc

[C] 왜 문자열은 포인터와 배열을 섞어서 쓰는 것일까? (두 개의 차이점)

C를 programming language structure 적으로 분석해보면 writability가 쓰레기인 언어라서 string을 primitive data로 지원하지 않는다. 하도 욕을 먹어서 C++에서 생긴 것인가?

 

아무튼 그래서 C에서 string을 표현하고자 할때는 그냥 char를 배열로 나열해서 쓰는 수 밖에 없다. 음 역시 writability 쓰레기!

 

근데 또 짜증나는 부분은 여기다...

아니 뭐 그래서 string를 배열로 표현하라며 근데 왜 포인터로도 가능한데?

라는 것이다.

 

이걸 설명하자면 programming language structure적으로 분석할 수 있다.

일단 C에서 array와 pointer는 왔다 갔다리 개발자 마음대로 쓸 수 있음

근데 왜 그럴까? 왜 C에서는 포인터와 배열을 섞어서 쓸 수 있을까?

 

 

 

일단 C에서의 string을 알아보자

string은 길이 구현에 따라 여러 descriptor들을 가진다. string의 길이를 언제 정하는지에 따라 필요한 descriptor가 달라지기는 한다.

descriptor라는 것은 attributes of variable, 즉 속성들을 모아둔 것이다. 이걸로 type checking 도 하고 메모리에 alloc/dealloc도 한다.

 

string의 길이 구현은 세가지로 나눠서 한다.

1. static character string: compile time에 길이를 정하고 바꾸지 않음. 그 안의 값은 범위를 벗어나지 않는 한 니 마음대로 바꿔도 된다.

2. limited dynamic string: compile time에 최대 길이를 정하고 run time에는 자유롭게 바꾸기 가능하다. -> C/C++은 여기다.

3 dynamic string: 니 맘대로 해 run time에 제한없이 줄였다가 늘렸다가 가능하다.

 

static character string은 compile time에 길이가 정해지기 때문에 compile time descriptor를 compile time에 가지고 있다가 compile이 끝나면 버려도 된다. 즉 run time에는 필요 없는 정보다. 왜냐면 변하지 않으니까요 그래서 왼쪽 그림 처럼 세가지 정보만 가지고 있으면 된다. string, length, address

근데 limited dynamic string은 길이를 compile time에 정하기는 하는데 run time에 바뀔 수 있다. 그래서 run time descriptor를 가지고 있어야 한다.. 오른쪽 그림에 해당됨. string, 최대 길이, 현재 길이, address 정보가 run time에도 필요하다.

 

근데 여기서 나타나는 C의 문제.

 

1. C는 maximum length 정보가 필요 없음

C는 string이 길이를 벗어나는지 boundary check를 안한다.. 그래서 maximum length 정보가 필요 없다.

 

2. C는 current length 정보를 유지하지 않음

C는 문자열이 끝났다는 것을 알려주기 위해서 항상 마지막에 \0 null character를 넣어준다.

그래서 걍 null이 끝에 있으니까 길이는 니들이 알아서 구해~ 하는 것이다.

 

위 두가지 이유로 run time descriptor의 정보가 address만 남고, 그래서 run time descriptor 정보를 유지 하지 않는다.

 

 

 

 

c에서 array라는 data type은 compile time descriptor를 가진다. 그래놓고 run time에는 이를 버려서 run time descriptor를 유지하지 않는다. 그냥 symbol table 이라고 생각하면 된다.

근데 여기서 C의 문제.

1. C는 array boundary check을 안함. 

따라서 lower bound, upper bound의 정보가 필요 없음.

예를 들어서

 

int a[10];

a[10]=2;

 

근데 사실 이건 설계에서부터 잘못된 것이다. 범위는 0~9 까지니까... 교수님께서 항상 C를 비판하시는 이유

근데 C는 이걸 check를 안해서 꼭 run time에 죽어버림

 

2. C의 Index type은 integer만 가능함

index type은 integer만 가능하기 때문에 index type 정보도 필요 없음.

 

따라서 compile time descriptor에 element type, address만 가지고 있음.

결국 남은 것은 type과 address인데

 

type과 address? 자료형과 주소???

= 포인터와 같다.

포인터도 자료형이랑 주소값 정보만 가지고 있기 때문

 

그래서 포인터와 배열을 섞어서 쓸 수 있는 것이다. 문자열에만 한정된 것이 아니라 그냥 배열과 포인터는 호환 가능이다.

 

 

그럼 문자열을 포인터와 배열을 섞어서 썼을 때 차이가 없을까?

아니다.... 사실 문자열을 포인터와 배열로 쓰면 분명한 차이가 생기게 된다.

어디서 생기는 것인가? 바로 수정 가능 여부에서 차이가 생긴다.

 

일단 문자열은 어디에 저장되는지 알 필요가 있다.

 

메모리 구조가 이렇게 생겼는데, 문자열은 code 영역에 생성된다.

근데 code 영역은 다들 알다시피 read only, 즉 수정 불가다. 어떤 경우라도 개발자는 접근하지 못한다.

 

만약 문자열을 포인터로 선언했다고 하자. 예를 들어

char *ptr="hello";

코드를 썼다고 하면 "hello" 는 code 영역에 있고 ptr는 char 타입의 포인터 변수다.

따라서 ptr은 code 영역에 있는 "hello"의 시작 주소 정보를 stack에 저장한다. (ptr이 local이라고 하자)

그럼 내가

*ptr="a";

라고 하면 가능할까?

절대 불가능. 왜냐면 ptr은 code 영역의 주소를 가리키는 것이고, 이는 수정 불가하기 때문이다.

 

그럼 이번에는 문자열을 배열로 선언했다고 해보자. 예를 들어 

char ptr[]="hello";

이 경우 "hello"는 여전히 code 영역에 존재한다. 근데 이 code 영역에 존재하던 "hello"를 stack으로 복사해서 stack에 "hello"를 만들어준다. 즉 "hello"가 두 개가 되는 것이다. 그리고 stack에 만들어진 "hello"를 배열로 관리하는 것이다. 

ptr[0] -> "h"

ptr[1] -> "e"

그렇기 때문에

ptr[2]='i'

이것도 가능한 것이다. 왜냐면 stack 영역에 있으니 얼마든지 수정 가능하기 때문이다.

 

어쨌든 이런 차이들을 분명히 알아놔야 나중에 compile error가 떠도 당황하지 않는다.

 

 

 

 

 

'STUDY > etc' 카테고리의 다른 글

[디버그일기] C2011 - visual studio 쓰기 싫다  (0) 2020.11.19