포인터 문제중에....

fliers의 이미지

char s[100];
char *p;

라고 돼 있을때 틀린것 찾는건데,

1. s=p;
2. p=s;
3. *p=*s;
4. *s=(*p++);

처음에 1번은 되는건줄 알고 생각도 않하고 넘어갔었는데 1번이 답인것 같더군요.

s 주소에 p 주소를 넣는다 같은데 뭐가 틀렸는지 모르겠네요..
s가 배열로 선언돼 있는건데 p가 들어오면서 배열이 깨지나요?

File attachments: 
첨부파일 크기
Image icon 배열강좌5-3.gif9.27 KB
Image icon 배열강좌5-2.gif15 KB
Image icon 배열강좌5-1.gif14.9 KB
bugiii의 이미지

배열 변수명은 아예 그자체가 (배열의 내용이 아닌) 상수인 포인터라고 생각하시면 편합니다. 상수는 값을 변경 못하니까 상수 포인터도 가리키는 주소값을 변경 못하는 것이라고 생각하시면 됩니다.

const char* a; // 포인터가 가리키는 곳의 값을 변경할 수 없습니다. 주소는 변경 가능합니다.
char const* b; // 마찬가지구요.
char* const c; // 가리키는 곳의 값은 변경할 수 있지만 가리키는 주소는 변경할 수 없습니다.
const char* const d; // 둘다 변경할 수 없습니다.

배열은 3번째와 마찬가지라고 보시면 되겠습니다. 각각의 차이점을 조사해보시면 도움이 되실 것입니다.

그리고, 위 문제는 문구중에 문법만으로라는 단서 조항이 있어야 합니다. 그런 말이 없다면 진짜 맞는 것은 2번밖에 없군요.

fliers의 이미지

감사합니다.. 그렇게 생각하니깐 좀 낫네요.. :)

lsj0713의 이미지

bugiii wrote:
배열 변수명은 아예 그자체가 (배열의 내용이 아닌) 상수인 포인터라고 생각하시면 편합니다. 상수는 값을 변경 못하니까 상수 포인터도 가리키는 주소값을 변경 못하는 것이라고 생각하시면 됩니다.

첨언하자면, 대입 연산자 '='의 왼쪽 항에 올 수 있는 것은 modifiable lvalue 뿐입니다. 배열 이름은 lvalue이지만 modifiale lvalue가 아니기 때문에 대입 연산자의 왼쪽 항에 올 수가 없습니다.

그리고 배열 이름은 다음 세가지 경우를 제외하고는 항상 그 배열의 첫번째 원소를 가리키는 포인터 주소 값으로 자동 변환 됩니다. 이 특성 때문에 보통 '배열 이름은 포인터 상수다'라고 설명을 하곤 합니다.

* sizeof 연산자의 피연산자 - int array_int[5]; sizeof(array_int);
* 번지 연산자의 피연산자 - int array_int[5]; &array_int;
* 문자형 배열 초기화에 사용되는 문자열 상수 - char str[] = "abcde";

참고로 3번째 경우에 대해 설명을 하자면, "abcde" 와 같은 문자열 상수(string literal)도 배열 이름과 비슷한 특성을 갖습니다.

strcpy(buffer, "abcde");

여기서 strcpy함수의 두번째 매개변수로는 "abcde" 중에서 a를 가리키는, const char *형의 포인터 주소값이 넘어가게 됩니다. 다만 위에서 설명한 대로, 문자형 배열 초기화에 사용되는 문자열 상수는 strcpy와 비슷한 동작을 보입니다. 배열 안에 자신의 내용을 복사하는 거죠.

bugiii wrote:
그리고, 위 문제는 문구중에 문법만으로라는 단서 조항이 있어야 합니다. 그런 말이 없다면 진짜 맞는 것은 2번밖에 없군요.

아닙니다. 1번을 제외하고는 모두 맞는 문장이며, 실제로 쓰일 수 있습니다.

char s[100];
char *p;

1. s=p;
2. p=s;
3. *p=*s;
4. *s=(*p++);

2번은 s가 그 자신의 첫번째 원소를 가리키는, const char *형 포인터 주소값으로 변형되기 때문에 당연히 허용 됩니다.

3번은 p가 가리키는 char형 변수에다가 s의 첫번째 원소의 값을 대입하라는 뜻입니다. s가 "abcde..."였다면 p가 가리키는 char형 변수에는 'a'가 들어가게 되겠죠.

4번 또한 아무 문제 없습니다. s의 첫번째 원소에다가 p가 가리키는 char형 변수의 값을 대입하고, 포인터 p의 값을 1 증가시키라는 구문입니다. 물론 여기에는 전제조건이 있는데, p = malloc(n); 또는 char t[100]; p = t; 와 같은 구문이 중간에 끼어 있어야 한다는 것입니다.

전웅의 이미지

lsj0713 wrote:
그리고 배열 이름은 다음 세가지 경우를 제외하고는 항상 그 배열의 첫번째 원소를 가리키는 포인터 주소 값으로 자동 변환 됩니다. 이 특성 때문에 보통 '배열 이름은 포인터 상수다'라고 설명을 하곤 합니다.

어떠한 방법을 사용하든 올바른 규칙을 익히면 상관 없다고 생각합니다만,
개인적으로는 포인터 "상수" 라는 개념에 다소 반감을 가지고 있습니다. C
에서 "상수" 는 메모리에 존재하지 않습니다 (그렇다고 약속합니다). 배열
은 분명 메모리에 존재하는 개체이며, 그러기에 포인터 연산에 관련되어 그
요소가 modifiable lvalue 가 될 수도 있습니다.

lsj0713 wrote:
* 문자형 배열 초기화에 사용되는 문자열 상수 - char str[] = "abcde";

참고로 3번째 경우에 대해 설명을 하자면, "abcde" 와 같은 문자열 상수(string literal)도 배열 이름과 비슷한 특성을 갖습니다.

넵, 더 리얼(?)하게는,

#define num2hexdigit(i) ("0123456789ABCDEF"[i])

도 가능합니다.

lsj0713 wrote:
1번을 제외하고는 모두 맞는 문장이며, 실제로 쓰일 수 있습니다.

char s[100];
char *p;

1. s=p;
2. p=s;
3. *p=*s;
4. *s=(*p++);

2번은 s가 그 자신의 첫번째 원소를 가리키는, const char *형 포인터 주소값으로 변형되기 때문에 당연히 허용 됩니다.

3번은 p가 가리키는 char형 변수에다가 s의 첫번째 원소의 값을 대입하라는 뜻입니다. s가 "abcde..."였다면 p가 가리키는 char형 변수에는 'a'가 들어가게 되겠죠.

4번 또한 아무 문제 없습니다. s의 첫번째 원소에다가 p가 가리키는 char형 변수의 값을 대입하고, 포인터 p의 값을 1 증가시키라는 구문입니다. 물론 여기에는 전제조건이 있는데, p = malloc(n); 또는 char t[100]; p = t; 와 같은 구문이 중간에 끼어 있어야 한다는 것입니다.

간단히

char t; p = &t;

여도 됩니다.

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

vacancy의 이미지

Quote:
어떠한 방법을 사용하든 올바른 규칙을 익히면 상관 없다고 생각합니다만,
개인적으로는 포인터 "상수" 라는 개념에 다소 반감을 가지고 있습니다. C
에서 "상수" 는 메모리에 존재하지 않습니다 (그렇다고 약속합니다). 배열
은 분명 메모리에 존재하는 개체이며, 그러기에 포인터 연산에 관련되어 그
요소가 modifiable lvalue 가 될 수도 있습니다.

배열의 요소들은 메모리에 존재하는 개체지만,
배열의 이름(즉, 배열의 첫번째 원소를 가리키는 포인터)은
상수로 사용되지 않는지요 ?
그래서 배열의 이름은 modifiable lvalue가 안될것 같은데,
그 이유로 첫번째 명령이 안되는 것 아닌가요 ?

바이너리 코드를 확인해본적이 없어서 -_-;
자세한 설명을 부탁드리겠습니다. ^^;

mastercho의 이미지

데브피아의 옹언욱님 강좌란에서 발취했습니다

Quote:

-------------------------------------
2. 문제의 해결 1
-------------------------------------

char *pTest= "01ABC";
char cTest[]= "01ABC";

이 두 라인의 차이점이 우리가 해결한 문제였지요.

위 코드를 컴파일하여 EXE 파일을 얻으면.. EXE 파일 내부에는 "01ABC" 라는 스트링이 들어가있습니다.

무슨말이냐믄...

C언어로 코딩하실때.. 큰따옴표 안의 스트링은... EXE 파일로 만들어지면.. 죄다 EXE 파일에 뒷쪽에 들어가게됩니다.

(EXE 이미지에서 스트링은 코드부분에 들어가있지 않고 따로 존재합니다.
데이터 세그먼트라고 부르는 구간에 들어가있습니다.)

여튼... "01ABC" 라는 문자열은 EXE 파일 내부에 있고.. 프로그램 실행시에는 EXE 파일 자체가 번역없이 프로세스에 로딩됩니다.

그 로딩된 EXE 이미지의 "01ABC" 라는 문자열이 런타임시엔 상수처럼 사용되지요..

위 코드에서 pTest는 단지 런타임시에 4 바이트 포인터 변수로서 스택에 할당되고

단지 이 변수에는 "01ABC" 라는 스트링이 시작되는 주소를 넣는 코드입니다.

동적할당하고는 관계가 없고

그 pTest에 담겨진 주소는.. 프로세스에 로딩되어있는 EXE 이미지 구간 중 한부분입니다.

그리고..

cTest는 런타임시 스택에 6바이트가 할당되고

프로세스의 EXE 이미지 구간에서 "01ABC" 라는 문자열을 복사해옵니다.

결과적으로 pTest 가 포인팅하고 있는 메모리는 EXE 이미지인 것이며..

위의 코드에서는 pTest가 포인팅 하는 메모리는 할당된 힙이 아니라, 바로 EXE 이미지의 데이터 구간중 한 곳입니다.

위의 코드 바로 아래다가

strcpy(pTest, "안녕"); // 포인터변수 pTest

저렇게 코딩하게 되면.. 컴파일 할때는 오류가 검사되지 않으나.. (문법상으로는 오류가 없기 때문입니다.)

런타임시에는 프로그램이 저 코드 부분에서 바로 죽어버립니다.

pTest 가 포인팅 하고 있는 메모리는 EXE 이미지이기 때문입니다.

EXE 이미지 구간은 오버라이트를 하면 안되죠..

DOS 플랫폼 같았으면 프로그램이 알수없게 동작하거나.. 시스템 자체가 먹통이 되버리는 상황이 발생 되었을 일입니다.

하지만

strcpy(cTest, "안녕"); // 배열변수 cTest

이런 코드는.. 런타임 오류가 발생하지 않습니다..

이미 cTest 는 6 바이트라는 영역이 스택에 할당되어있기 때문입니다..

그 안엔 이미 "01ABC"라는 문자열이 복사되어져 와있습니다.

6바이트 이내로 안전하게.. 오버라이트가 가능합니다.

그리고 참고로.. pTest 라던가 cTest 라던가하는 변수 이름은..

네이티브 코드가 만들어질때.. 변수의 이름 자체는 의미가 없습니다..

단지 C언어라는 고급 프로그래밍 언어에서

소스 코드 작성하기 편리하도록 지원해주는 것일 뿐입니다.

변수라는 것은 프로그램 런타임시 단지 주소값을 갖는 메모리의 일 부분일 뿐입니다.

-------------------------------------
3. 문제의 해결 2
-------------------------------------

char *pTest= "01ABC";
char cTest[]= "01ABC";

printf("pTest : %08x %08x \n", pTest, &pTest);
printf("cTest : %08x %08x \n", cTest, &cTest);

출력 결과----------

pTest : 00407058 0012ff78
cTest : 0012ff7c 0012ff7c

출력결과는 코딩을 어케 했느냐와.. 시스템에 따라 달라질 수 있습니다.

하지만 모든 시스템에서 두번째 라인에 출력되는 값 두개는 서로 동일합니다...

왜 두개가 서로 동일한 것인가가.. 우리가 풀어볼 문제였지요..

출력결과 pTest 를 보면

첫번째 수치 0x00407058 은 pTest 변수에 담긴 값입니다..

pTest에 담긴 이 값은 메모리 주소를 의미하고..

이 메모리 주소는 프로세스에 로딩된 실제 EXE 이미지의 한부분이죠. (위에서 설명한 말입니다.)

두번째 강좌에서 0x00400000 부터 EXE 이미지가 로딩된다고 설명했었고..

실제로 pTest가 포인팅하는 주소는 0X0040000 과 매우 근접한 것을 확인 하실 수가 있습니다. #### 첨부된 그림 1 참조 ####

실제로 EXE 파일 덤프해보면 0x7058 오프셋부터 "01ABC" 라는 데이터가 있는 것도 확인됩니다. #### 첨부된 그림 2 참조 ####

그리고 두번째 수치 0x0012FF78 의 경우는 pTest 라는 변수가 할당된 스택 메모리의 주소입니다.

출력결과 cTest 를 보면.. (이것이 우리가 풀어볼 문제지요..)

좀 특이한데...

배열 변수는 [] 사용없이 배열 변수명만을 사용하면

변수명 자체가 그 배열변수가 스택에 위치한 주소값을 나타냅니다.

그 cTest 에 담겨 있는 값은 분명 "01ABC" 의 첫바이트인 '0' 이라는 값으로 봐야하지만..

cTest 라고 코딩하면 이것은 cTest안에 담긴 값이 아니라 cTest 자신의 주소값을 의미합니다.

결과적으로 C 로 코딩할때, char cTest[]="01ABC"; 라고 선언된 배열변수에 대하여

cTest는 &cTest 와 의미가 같고

둘다 &cTest[0] 을 의미하는 것 입니다..

이것이 배열이 포인터와는 다른 점입니다.

달라도 전혀 다른 것이지요.

포인터변수는 자신에게 담긴 값으로.. 다른 메모리를 포인팅 해주지만

배열변수는 스택에 메모리 배열이 할당되고, 변수명 자체가 자신의 주소값을 의미합니다.

이해를 돕고자 그림을 하나 그려보았습니다..

#### 여기서 첨부된 그림 3 을 봐주세요 ####

이제 좀 응용해서 설명해 보겠습니다.

(pTest + 2) 은 메모리에서 'A'가 담긴 주소값을 의미하고 (0x0042206C + 2) --> 32비트 수치 메모리 주소

pTest 라는 변수에는 주소값이 담겨있기 때문에 메모리 주소값에 2를 더해주는 것입니다.

그래서 pTest[2] 는 *(pTest + 2)를 의미합니다. --> 8비트 수치 char 형 'A'

(cTest + 2) 도 메모리에서 'A'가 담긴 주소값을 의미합니다. (0x0012FF70 + 2) --> 32비트 수치 메모리 주소

하지만 cTest 라는 변수에는 메모리 주소값이 담겨있지 않습니다. 단지 cTest 가 &cTest를 의미하는 것입니다.

그래서 cTest[2] 의 경우에도 *(cTest + 2)를 의미하는 것이지만 --> 8비트 수치 char 형 'A'

cTest 엔 메모리 주소값이 담겨있는게 아니라 cTest 는 &cTest 와 동격인 것입니다.

지금까지 제가 한 말이 못미더우시면..

비주얼 씨의 디버깅 기능중에

프로세스 영역을 보여주는 기능이 있습니다..

View > Debug Window > Memory

프로그램 런타임상태에서.. 브레이크 포인트가 걸리게 하신담에

메모리 윈도우를 열으시고.. 메모리 주소를 트래이싱해 보시면

직접 확인해 보실 수 있습니다.

-------------------------------------
4. 포인터 연산자
-------------------------------------

이 소단원은 그냥 참고 용으로 써보는 내용입니다.

포인터변수의 선언과 포인터 연산자는.. 서로 다릅니다..

이미 포인터를 학습해 오시고 사용해 오시면서.. 익히 아실 것으로 압니다.

포인터의 선언은 포인터 변수를 선언 하는 것이고

(반복해서 설명해 드리지만.. 선언된 변수 자체는 런타임시 32비트 사이즈로 스택에 잡힙니다.

그리고 그 변수는 주소값을 담고 있으면서, 할당된 힙블럭을 포인팅한다거나 EXE 이미지의 데이터를 포인팅하는 것입니다.)

포인터 연산자는 어느 수치값(주소)이 의미하는 메모리주소에 담겨있는 실제 값을 얻어오거나 할때 사용하게 됩니다.

아래 코드에서 (char*) 는 캐스팅이고... 재일 왼쪽에 붙은 별이 포인터 연산자입니다.

char cA= *((char*)100);

위와 같은 코드는 메모리주소 100 에 담긴 값이 cA 라는 변수로 바로 얻어집니다. (실제 이런 코드는 사용안되지지만..)

WIN32에서 메모리 포인터는 죄다 32 비트라고 했는데

char *pTest; 라고 선언된것에서 이 char 타입이.. 바로 포인터연산자에 활약하는 것입니다..

short *Test; 라고 선언한 것에 포인터 연산자를 사용하면 2 바이트 short 값으로 얻어오게 되지요..

그리고 [] 자체도 연산자입니다. 포인터 연산자와 같은 동작을 합니다.. 명칭은 까먹엇어요.

ㅋㅋㅋ

언제나 드리는 말씀이지만.. 용어를 아는것 보다 원리를 아는 것이 더 중요합니다..

사람들이 규정한 용어라는 것은.. 원리를 이해하고 있다면 그냥 사전 찾아보시믄 됩니다.

-------------------------------------
5. 정리
-------------------------------------

이처럼.. 배열과 포인터변수는 코딩상에서 사용은 서로 유사하지만..

사실 그 내부 동작은 매우 차이가 납니다.

이러한 차이 때문에 발생하는 문제에 대해서도 말씀드렸구요..

우리가 의식하지 못했던.. 스택이라는 것이 존재하기 때문입니다..

이것을 이해하는 것은 자신의 스킬을 한단계 끌여 올릴 수 있는 계기가 될 것입니다.

이강좌를 완독하셨다면..

지금까지 코딩하실때 그냥 그냥 사용했던 포인터 변수들과 배열/ 함수호출/ 함수진입 등이

조금은 다른 느낌으로 가슴에 와 닿으실 거라 생각됩니다.

-------------------------------------
6. 마치며
-------------------------------------

이제 막을 내릴 시간입니다.

이번 강좌는 지금까지 써왔던 강좌와는 좀 다르게

해결하기 위한 문제를 먼저 제시해 보고

그 문제를 풀기 위해 필요한 지식들을 설명해 나가는 방식으로 진행해 보았습니다..

기반 지식을 갖추게 되면 풀리는 문제이기 때문이기도 합니다.

스택에 대해서 용어만 알고 계셨던 분이라거나.. 사전적인 의미만 알고 계셨던 분이라거나..

여기 까지 읽으셨다면.. 감을 잡으셨을거라 생각이 됩니다..

중요하고 기초적인 것이면서도 왠만해서는 잘 안다뤄지고 있는 내용이라 써봤어요

"배열과 포인터는 전혀 다르다" 라는 제목으로 작성된 이 강좌에는

WIN32 플랫폼, 가상메모리, 프로세스, EXE 이미지, 프로그램 구동원리, 스택, 힙을 설명하였습니다.

강좌 제목은 여러분의 이목을 끌어보기 위한 타이틀이였고,

첫 회에 말씀드린 것 처럼 주로 메모리와 관련된 이야기였습니다.

다루어진 항목은 많은데 각 항목에 대해 세밀한 부분까지는 설명드리지 못 한것 같은 생각이 들어요..

제 딴에는 쉽게 풀어서 쓴다고 썻지만..

모호한 설명이라거나 난해하게 표현된 부분이 있을지도 모르구요..

글을 등록하고 난 후에도.. 발견하는 대로 수정하도록 하겠습니다.

-------------- 다시 읽어보고 글 수정합니다 -_-;;------------

cTest를 보면 자체가 주소값 이기때문에 L-value로는 쓰이지 못하는게 아닌가 싶네요

예를 들면 12345 = 456; 이렇게 대입 못하듯이요

따라서 상수로 볼수 있는게 아닌가 싶네요

C언어 책에도 상수처럼 쓰인다 -_-;라고 자주 언급되어집니다
이건 보는 사람관점에 따라 달라질수 있는 문제긴 하지만요

댓글 첨부 파일: 
첨부파일 크기
Image icon 0바이트
Image icon 0바이트
Image icon 0바이트

승자는 자기보다 우월한 사람을 보면 존경심을 갖고 그로부터 배울 점을 찾지만 패자는 자기보다 우월한 사람을 만나면 질투심을 갖고 어디 구멍난 곳이 없는지 찾는다.
- 하비스

전웅의 이미지

vacancy wrote:
배열의 요소들은 메모리에 존재하는 개체지만,
배열의 이름(즉, 배열의 첫번째 원소를 가리키는 포인터)은
상수로 사용되지 않는지요 ?
그래서 배열의 이름은 modifiable lvalue가 안될것 같은데,
그 이유로 첫번째 명령이 안되는 것 아닌가요 ?

바이너리 코드를 확인해본적이 없어서 -_-;
자세한 설명을 부탁드리겠습니다. ^^;

"바이너리 코드" 를 확인할 정도로 low-level 로 간다면 C 언어의 거의 모
든 개념이 사실상 무의미해집니다 - C 언어의 정의는 "추상적" 관점에서 관
찰한 C 프로그램의 행동입니다.

제가 개인적으로 "포인터 상수" 라는 개념에 반감을 갖는다고 말씀드린 것
은 "포인터 상수" 라는 개념을 갖고 있다는 사실 자체가 실제 프로그래밍
과정에서 큰 잘못을 낳는다는 뜻으로 드린 말씀은 아닙니다. 다만, "포인터
상수" 라는 개념이 "상수" 의 의미를 포함하기에 C 언어의 lvalue 에 대한
개념을 이해하는 과정에서 방해가 될 수 있다는 지극히 "교육적인" 입장에
서 드린 말씀입니다.

예를 들어, 어떤 분이 언급하신 것처럼

int a[10], b[10];
a = b;

가 허락되지 않는 이유가

100 = 200;

가 허락되지 않는 것과 동일하다면,

&a;
&100;

위는 허락되고 아래는 허락되지 않는 이유를 설명할 길이 없어집니다. 또한
배열 이름이 포인터 수식에서 단지 첫번째 요소의 주소값인 "포인터 상수"
일 뿐이라면, &a 의 type 이 pointer to "array" of int 라는 사실을 설명
할 길도 없어집니다 - 즉, "array" 의 의미를 그대로 보존하는 것이 가능하
다는 사실을 설명할 수 없습니다. 배열은 modifiable lvalue 는 아니어도
lvalue 입니다. 그리고 상수는 lvalue 가 아닙니다 (물론, C99 에 와서 그
경계가 엉터리가 되었습니다만). C 언어의 lvalue 는 일반적인 프로그래밍
언어에서 사용하는 lvalue 와는 다른 개념으로, 일반적인 lvalue 에서 'l'
이 "left" 를 의미한다면, C 언어에서 lvalue 의 'l' 은 "locator" 를 의미
한다고 이해하는 것이 언어를 자연스럽게 따라가는데 유리합니다.

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

mastercho의 이미지

C++에서는
int* const p = 50;

에서 언어 차원에서 p가 상수 포인터가 되듯이

int A[50]; 에서 A가 상수 포인터로 인식된다고

생각하는것도 오류가 되나요??

저는 그러한 관점에서 접근한건데.. --;

승자는 자기보다 우월한 사람을 보면 존경심을 갖고 그로부터 배울 점을 찾지만 패자는 자기보다 우월한 사람을 만나면 질투심을 갖고 어디 구멍난 곳이 없는지 찾는다.
- 하비스

전웅의 이미지

mastercho wrote:
C++에서는
int* const p = 50;

에서 언어 차원에서 p가 상수 포인터가 되듯이

int A[50]; 에서 A가 상수 포인터로 인식된다고

생각하는것도 오류가 되나요??

저는 그러한 관점에서 접근한건데.. --;

언젠가 han.comp.lang.c 에서도 같은 내용을 말씀드린 적이 있습니다만,
"호박 고구마" (호박 맛이 나는 고구마) 와 "고구마 호박" (고구마 맛이 나
는 호박 - 이런 게 진짜 있는지는 모릅니다 ;-) 이 다른 의미를 갖듯이,
"포인터 상수" 와 "상수 포인터" 역시 다른 의미를 갖습니다. 그리고 저는
"포인터 상수" 라는 설명 (그래서, 배열에 이루어지는 대입이 다른 상수에
이루어지는 대입과 "마찬가지" 이유로 허락되지 않는다는 생각을 끌어낼 가
능성이 있는) 에 대해 제 의견을 말씀드린 것입니다.

그리고, "포인터 상수" 라는 생각 역시 (교육적인 관점에서 바람직하지는
않아도) "오류" 라고 생각하지는 않습니다 - 겉으로 보이는 동일한 현상을
추상적인 단계에서 해석하는 방법의 차이일 뿐이라고 생각합니다.

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

lsj0713의 이미지

전웅 wrote:
간단히

char t; p = &t;

여도 됩니다.

포인터가 가르키는 것이 배열이 아니라 t형 변수일 때에도, 그 다음의 공간을 가리키는 것이 허용이 되는지요? 배열이나 malloc로 할당된 공간의 경우에는 허용된다고 알고 있는데, 그냥 일반 변수일 경우에도 허용이 되는 것인지 알고 싶습니다.

lsj0713의 이미지

mastercho wrote:
cTest를 보면 자체가 주소값 이기때문에 L-value로는 쓰이지 못하는게 아닌가 싶네요

예를 들면 12345 = 456; 이렇게 대입 못하듯이요

따라서 상수로 볼수 있는게 아닌가 싶네요

C언어 책에도 상수처럼 쓰인다 -_-;라고 자주 언급되어집니다
이건 보는 사람관점에 따라 달라질수 있는 문제긴 하지만요

전웅님이 말씀하신 것처럼, C99에서의 lvalue는 좌변값이 아니라 Locator value 입니다. 기존에 쓰이던 용어인 좌변값에 해당하는 것은 C에서는 modifiable lvalue로 보는 것이 타당합니다.

용어 자체가 그렇게 크게 중요하다고는 할 수 없겠습니다만, C 표준 문서에는 배열 이름이 lvalue라고 분류되어 있습니다. 누군가 "C 표준문서에는 배열이 lvalue라고 나와있던데?"라고 묻는다면 어떻게 설명해야겠습니까?

전웅의 이미지

lsj0713 wrote:
전웅 wrote:
간단히

char t; p = &t;

여도 됩니다.

포인터가 가르키는 것이 배열이 아니라 t형 변수일 때에도, 그 다음의 공간을 가리키는 것이 허용이 되는지요? 배열이나 malloc로 할당된 공간의 경우에는 허용된다고 알고 있는데, 그냥 일반 변수일 경우에도 허용이 되는 것인지 알고 싶습니다.

네, 가능합니다. 단일 대상체는 포인터 연산에서 1 의 크기를 갖는 배열과
동일하게 다루어집니다. 이 내용이 어디에 있는가하면... 음... C90 의 6.3
expression (C99 의 6.5) 의... 음... additive operatior 설명을 보면...
음... 포인터 연산에 대해 구구절절 긴 설명이 있는 부분의 첫 부분에

"단일 object 는 크기 1 의 배열과 동일하게 다룬다"

라는 식의 비슷한 이야기가 있을 것입니다.

p.s. 자료를 노트북으로 모두 옮겨 놓앗더니, 데스크탑에 앉아서 할 수
있는 일이 거의 없군요. :-(

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

pynoos의 이미지

mastercho wrote:
C++에서는
int* const p = 50;

에서 언어 차원에서 p가 상수 포인터가 되듯이

int A[50]; 에서 A가 상수 포인터로 인식된다고

생각하는것도 오류가 되나요??

저는 그러한 관점에서 접근한건데.. --;

C와 C++에서는 const의 생각을 약간 달리하셔야합니다. readonly(C)이냐, constant (C++) 이냐의 성격을 지닙니다.

제가 이해하기로는 readonly인경우, object가 항상 생성되는 것이며(C에서), C++에서는 기본적으로는 readonly object가 생성되겠지만, constant hint를 주기 때문에 최적화시에 그 pointer를 reference하지 않을 경우 object에서 공간을 차지하지 않고, 바로 사용되는 곳에 embed 되어버립니다.
물론 C 컴파일러에서도 reference하지 않는다면 object를 생성하지 않을 수 있습니다.

데브피아의 옹언욱님 강좌란에서 wrote:

-------------------------------------
3. 문제의 해결 2
-------------------------------------

char *pTest= "01ABC";
char cTest[]= "01ABC";

printf("pTest : %08x %08x \n", pTest, &pTest);
printf("cTest : %08x %08x \n", cTest, &cTest);

이와 같이 과연 배열 이름을 나타내는 값이 어떤 주소에 할당되느냐 안되느냐의 문제를 알기위해 예를 드는 프로그램들은 대개 pointer를 찍어보는 것 때문에 이런 최적화가 무시될 수가 있습니다.

실제로 배열을 상수 포인터라는 개념으로 이해하는 것은 타당합니다. 다만, 그 상수라는 개념이, "pointer를 취하는 일이 없다면 object로 만들지 않을 수 있다." 를 내포할 때만, 그렇게 이해하는 것이 좋다 생각됩니다.

lsj0713의 이미지

pynoos wrote:
mastercho wrote:
C++에서는
int* const p = 50;

에서 언어 차원에서 p가 상수 포인터가 되듯이

int A[50]; 에서 A가 상수 포인터로 인식된다고

생각하는것도 오류가 되나요??

저는 그러한 관점에서 접근한건데.. --;

C와 C++에서는 const의 생각을 약간 달리하셔야합니다. readonly(C)이냐, constant (C++) 이냐의 성격을 지닙니다.

제가 이해하기로는 readonly인경우, object가 항상 생성되는 것이며(C에서), C++에서는 기본적으로는 readonly object가 생성되겠지만, constant hint를 주기 때문에 최적화시에 그 pointer를 reference하지 않을 경우 object에서 공간을 차지하지 않고, 바로 사용되는 곳에 embed 되어버립니다.
물론 C 컴파일러에서도 reference하지 않는다면 object를 생성하지 않을 수 있습니다.

데브피아의 옹언욱님 강좌란에서 wrote:

-------------------------------------
3. 문제의 해결 2
-------------------------------------

char *pTest= "01ABC";
char cTest[]= "01ABC";

printf("pTest : %08x %08x \n", pTest, &pTest);
printf("cTest : %08x %08x \n", cTest, &cTest);

이와 같이 과연 배열 이름을 나타내는 값이 어떤 주소에 할당되느냐 안되느냐의 문제를 알기위해 예를 드는 프로그램들은 대개 pointer를 찍어보는 것 때문에 이런 최적화가 무시될 수가 있습니다.

실제로 배열을 상수 포인터라는 개념으로 이해하는 것은 타당합니다. 다만, 그 상수라는 개념이, "pointer를 취하는 일이 없다면 object로 만들지 않을 수 있다." 를 내포할 때만, 그렇게 이해하는 것이 좋다 생각됩니다.

C 표준에서는 실제적인 구현 방식에 대해 어떠한 제한도 하지 않고 있습니다. 의미적으로 표준이 보장한 바와 같은 동작이 이루어지도록 보장만 한다면, 내부적으로 어떠한 최적화를 하더라도 상관이 없습니다. 따라서 최적화의 관점에서 C를 이해하려는 시도는 의미가 없습니다. 애초에 아무것도 보장된 바가 없기 때문입니다.

char *pTest= "01ABC"; 
char cTest[]= "01ABC"; 

printf("pTest : %08x %08x \n", pTest, &pTest); 
printf("cTest : %08x %08x \n", cTest, &cTest);

저는 이 예제가 대체 무엇을 위한 예제인가 하는 의문이 듭니다.

위에 제가 쓴 글에서 나왔듯이, 배열 이름은 3가지 경우를 제외하고는 그 배열의 첫번째 원소를 가리키는 주소로 자동 변환이 됩니다. 따라서 cTest는 '0'을 가리키는 포인터 주소값이 됩니다. 반면에 &cTest는 3가지 경우에 해당하므로 배열 전체를 가리키는 포인터 주소값이 됩니다.

위 코드의 결과물은 C의 내부적인 구현 방식과는 전혀 관계가 없고, 어느 환경에서나 항상 같은 결과물이 나오는 그런 코드입니다. (물론 주소값이나 주소값의 출력 방식 등등은 환경에 따라 차이가 있을 수 있겠지요.)

그리고 아래의 설명은 틀렸습니다.

Quote:

cTest는 &cTest 와 의미가 같고

둘다 &cTest[0] 을 의미하는 것 입니다..

cTest는 배열의 첫번째 원소를 가리키는 포인터 주소값이고, &cTest는 배열 전체를 가리키는 포인터 주소값입니다. 같은 주소값이지만 가리키는 방식이 서로 다릅니다. 타입으로 설명하자면 char *형과 char *[]형이라 할 수 있습니다.

한가지 더 지적하자면, printf 함수에 전달하는 포인터 주소값은 항상 void *형으로 형변환을 해서 넘겨줘야 합니다.

전웅의 이미지

lsj0713 wrote:
char *pTest= "01ABC"; 
char cTest[]= "01ABC"; 

printf("pTest : %08x %08x \n", pTest, &pTest); 
printf("cTest : %08x %08x \n", cTest, &cTest);

저는 이 예제가 대체 무엇을 위한 예제인가 하는 의문이 듭니다.

알려져서는 안 되는 엉터리 예제입니다.

lsj0713 wrote:
위 코드의 결과물은 C의 내부적인 구현 방식과는 전혀 관계가 없고, 어느 환경에서나 항상 같은 결과물이 나오는 그런 코드입니다.

pTest 와 cTest 는 같은 결과가 보장되지만, &pTest 와 &cTest 는 그렇지도
않습니다 - 서로 다른 type 입니다.

lsj0713 wrote:
한가지 더 지적하자면, printf 함수에 전달하는 포인터 주소값은 항상 void *형으로 형변환을 해서 넘겨줘야 합니다.

추가로 한가지 더 지적하자면, 대응하는 conversion specifier 는 %p 여야
합니다. 이곳의 글이 그쪽으로 넘어가면 유용할 수 있지만, 그쪽 글이 이쪽
으로 넘어오면 득보다 실이 많은 듯 합니다. :-(

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

lsj0713의 이미지

Quote:

pTest 와 cTest 는 같은 결과가 보장되지만, &pTest 와 &cTest 는 그렇지도
않습니다 - 서로 다른 type 입니다.

굳이 변명하자면, 제가 글을 작성할 때 글의 뜻이 제대로 전달되지 않도록 엉터리로 쓴 것 같습니다. -_-;; 제 언어 능력에 뭔가 문제가 많은 듯 합니다. -_-;

그 부분은 &pTest와 &cTest가 같은 결과를 가진다는 뜻이 아니었습니다. 그것보다는 그 예제 코드가 뭔가 내부적인 동작원리를 보여주기엔 부적합한 코드였다는 뜻이었습니다. 추상적인 동작 원리 만으로도 설명이 가능한 코드이고, 뭔가 내부 구현이나 동작 환경이 달라진다고 해서 다른 결과가 나오는 코드가 아니기 때문입니다.

그리고 %p 부분에 대해서는... 제가 %x를 %p로 잘못 본 결과였습니다. -_-;; 이놈의 대충 읽는 습관을 고쳐야 할텐데...

lsj0713의 이미지

이미 논의가 끝난 문제에 또다시 답글을 달게 되어 죄송합니다만... hclc에 올라온 글을 보던 도중에 이 주제와 관련된 예제 코드가 생각나서 다시 글을 올리게 되었습니다.

배열 이름을 포인터 상수라는 개념으로 보는 것이 바람직하지 않은 이유를 제시합니다.

#include <stdio.h>

int main(void)
{
    int ex[4];
    int (*p)[4];
    char c;
    
    p = &ex;
    
    ex[0] = 123;
    ex[1] = 124;
    ex[2] = 125;
    ex[3] = 126;
    
    printf("%p\n", (void*)(*p));
    printf("%p\n", (void*)(&(*p)[0]));

    printf("%d\n", sizeof(*p));
    printf("%d\n", sizeof(&(*p)[0]));
    
    printf("%d\n", **p);    // 여기에 주목
    printf("%d\n", *ex);
    
    scanf("%c", &c); 
    return 0;
}

"배열 이름은 포인터 상수이다"라는 설명만으로는 위의 코드에서 **p가 왜 문법적으로 옳은 코드이며 **p의 값이 123인지 설명할 수 없습니다. p가 pointer to array of int[4] 이므로 *p는 array of int[4]가 되는데, 그럼 **p에서 array에 *를 붙이는 것이 대체 무슨 의미가 있겠습니까?

오로지 "array(혹은 array의 결과값을 갖는 수식)는 sizeof 연산자의 피연산자, & 연산자의 피연산자, char형 배열의 초기화에 쓰이는 문자열 상수일 때를 제외하고는 항상 그 첫번째 원소를 가리키는 포인터로 자동 변환된다"라는 설명만이 이 코드에 대한 설명을 할 수 있습니다.

bugiii의 이미지

Quote:

bugiii 씀:
그리고, 위 문제는 문구중에 문법만으로라는 단서 조항이 있어야 합니다. 그런 말이 없다면 진짜 맞는 것은 2번밖에 없군요.

lsj0713 씀:
아닙니다. 1번을 제외하고는 모두 맞는 문장이며, 실제로 쓰일 수 있습니다.

'문법적'으로 라는 단서조항이라고 말씀드렸는데, 앞말을 싹뚝 잘라버리시면... 3번 4번의 경우 거의 확실하게 segment fault 가 나지 않을까요? 그런 점을 말씀드린건데 설명이 부족했습니다.

그리고 배열을 포인터와 유사한 개념으로 생각하는 것이 편하다라고 ('이다'가 아니라는) 말씀드렸는데, 추가적인 설명이 없다보니 여러 답변들이 나온 것 같습니다만, 오히려 훌륭한 답변으로 좋은 것 또 배우고 갑니다.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.