c 언어에서 구조체 포인터 변수의 맴버 접근에 대해서...

rlj1202의 이미지

일반적인 포인터 변수같으면 Test라는 구조체가 있을때 Test * test; 라는 변수가 있다고 하면 맴버를 접근할 때 test->맴버 로 접근하고, 이는 곧 (*test).맴버 와 같잖아요?
그런데 제가 jni 에서 JavaVM* vm 이라는 포인터에서 함수를 접근할때

(**vm).함수명;

으로 접근해야 실행할 수 있더군요.
분명 vm은 이중으로 포인터를 사용하고 있는것 같지 않은데...

그래서 생각해 보건데, 다음과 같은 일이 가능한가요?

int a = 123;
int *b = &a;

int c = b;

printf("%d", *c);

이런식으로 일반 변수에 주소값을 담고 그 변수에 주소값 참조명령을 사용할 수 있는건가요?

자바를 하다가 c언어를 하니 포인터가 제일 어렵네요.

익명 사용자의 이미지

C언어는 이식성을 갖춘 언어입니다. 대표적인 활용 사례가 바로 리눅스 커널이 되겠죠.
자바를 하셨다고 하니, 자바 역시도 그런 언어라는 건 아실 테죠. 하지만 두 언어가 이식성을 갖추는 방식은 제법 다릅니다.

자바는 JVM의 지원에 힘입어, 어떤 환경에서든 상당히 일관성 있고 프로그래머에게 친숙한 프로그래밍 환경을 제공해 줍니다.
C언어는 어떤 환경이든 포용할 수 있는 유연한 표준을 제공하며, 프로그래머에게 최소한의 보장을 해 줄 뿐 기반 환경을 그대로 노출시킵니다.

이러한 상반되는 전략은, 두 언어에서 int를 어떻게 정의하고 있는지만 봐도 확연히 드러나죠.

-Java-
The values of the integral types are integers in the following ranges:
For int, from -2147483648 to 2147483647, inclusive.
( https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.2.1 )

-C-
5.2.4.2 Numerical limits

1 An implementation is required to document all the limits specified in this subclause, which are specified in the headers and . Additional limits are specified in .

Forward references: integer types (7.18).

5.2.4.2.1 Sizes of integer types

1 The values given below shall be replaced by constant expressions suitable for use in #if preprocessing directives. Moreover, except for CHAR_BIT and MB_LEN_MAX, the following shall be replaced by expressions that have the same type as would an expression that is an object of the corresponding type converted according to the integer promotions. Their implementation-defined values shall be equal or greater in magnitude (absolute value) to those shown, with the same sign.

(중략)

— minimum value for an object of type int
INT_MIN -32767 // -(2^15 - 1)
— maximum value for an object of type int
INT_MAX +32767 // 2^15 - 1

( C99 표준. http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf 에서 최종 드래프트를 볼 수 있습니다. )

자바는 int의 표현 범위를 -2147483648 ~ 2147483647으로 단정짓고 있습니다. 어떤 머신에서든 이러한 int를 쓸 수 있게 보장하는 것은 JVM의 몫입니다.

C는 int의 표현 범위는 구현 환경에 맡기되(implementation-defined) 그 상한과 하한을 각각 INT_MAXINT_MIN과 같이 limits.h 헤더에 상수로 정의하여 프로그래머가 참조할 수 있도록 하고 있습니다. 덧붙여, int가 최소한 -32767 ~ +32767을 표현할 수 있어야 한다는 최소 요구조건도 담고 있지요. C 컴파일러를 만드는 사람은 이러한 요구조건을 만족하는 어떠한 방법으로도 int를 구현할 수 있습니다. (보통 머신의 1워드를 사용하지요.) 또한 이식성 있는 C 프로그램을 만들 때 가정할 수 있는 것도 오직 그뿐입니다.

쓸데없이 장황한 배경지식을 왜 늘어놓는가 하면, 비록 32비트 x86에서 int와 포인터가 동일한 크기(4바이트)를 가지는 등 서로 표현능력이 같다고 해도, 일반적으로는 그러한 가정을 할 수 없다는 말씀입니다. 멀리 가지 않아도 64비트 x86_64만 해도 그렇습니다. 보통 int는 여전히 4바이트이지만, 포인터는 8바이트가 되지요. 그 경우에 포인터를 int로 변환하면 값이 손실되는 게 당연한 일이겠죠.

짧게 줄이면, 아래 코드는 이식성에 문제가 있는 위험한 코드입니다.

int a = 123;
int *b = &a;
int c = b; // 이러한 암묵적 변환이 일단 허용되지도 않을 거에요.
// int c = (int)b; 라고 명시적으로 변환하면 컴파일은 일단 성공할 겁니다. 실행할 때 무슨 일이 일어날지는 모릅니다.
// 위에 언급한 64비트 x86_64 환경일 경우, 포인터 값이 잘리게 되어 손실됩니다.

한 가지 더. 역참조 단항 연산자 *는 포인터에 대해서만 적용 가능합니다. 그래서 설령 포인터를 int로 변환할 때 손실이 일어나지 않는다 해도 거기서 문제가 됩니다.

printf("%d", *c); // syntax error. c는 포인터가 아닙니다.
 
int *p = (int *)c; // 손실이 없었다고 가정하죠.
printf("%d", *p); // 포인터가 int로 변환되었다가 다시 돌아오는 과정에서 값 손실이 없었다면, 정상적으로 돌아갈 가능성이 있기는 합니다.
// 어쨌든 이식성 위반입니다.

자바에서 C언어로 왔을 때 포인터에서 어려움을 느끼고 계신다는 것은
1) 위에 언급한 C언어와 자바의 철학 차이를 이해하지 못하고 계시거나
2) C언어와 밀접하게 연관되어 있는 "컴퓨터 구조"에 대한 이해가 부족하거나
3) 혹은 둘 다거나
인 상황이니, C언어의 문법 같은 것에만 너무 몰두하지 마시고 다방면으로 공부하시는 것이 바람직합니다.

rlj1202의 이미지

그럼 제가 맨 처음에 의문을 가졌던 JavaVM* vm 의 함수를 참조할때 (**vm).함수; 로 해야 했던것은 어떤 연유로 그런것 인가요?

익명 사용자의 이미지

JavaVM 타입 자체가 사실 포인터 타입이기 때문이겠지요. 그래야만 *vm(JavaVM 타입)이 포인터일 테니까요.

저는 jni는 잘 모릅니다만, 문서를 조금 찾아보니...

=== 발췌( https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html ) ===
The JavaVM type is a pointer to the Invocation API function table. The following code example shows this function table.

typedef const struct JNIInvokeInterface *JavaVM;

(후략)
======

역시나. JavaVM 타입은 사실 const struct JNIInvokeInterface *이었군요.
typedef을 이런 식으로 쓰는 건 사실 초보자들을 다소 혼란스럽게 만드는 경향이 있는데, 뭐 별 거 없습니다.

typedef const int *pcint; // pcint 타입은 const int * 타입
int a = 0; // 그냥 int형 변수
int &p = &a; // 그냥 int *형 포인터
*p = 1; // 가능
const int &cp = &a; // const int *형 포인터. 포인터가 가리키는 것은 상수로 가정됨.
*cp = 2; // 불가능. 컴파일 안 됩니다.
pcint &cp2 = &a; // 위와 같음. pcint는 const int *와 같음
*cp2 = 3; // 역시 안 됩니다.

요약하자면, JavaVM* vm와 같이 vm을 선언하였다면, 사실 const struct JNIInvokeInterface ** vm와 같이 선언하신 셈이란 겁니다. 모르는 사이 이중 포인터를 사용하고 계셨던 셈이죠.

익명 사용자의 이미지

typedef const int *pcint; // pcint 타입은 const int * 타입
int a = 0; // 그냥 int형 변수
int *p = &a; // 그냥 int *형 포인터
*p = 1; // 가능
const int *cp = &a; // const int *형 포인터. 포인터가 가리키는 것은 상수로 가정됨.
*cp = 2; // 불가능. 컴파일 안 됩니다.
pcint cp2 = &a; // 위와 같음. pcint는 const int *와 같음
*cp2 = 3; // 역시 안 됩니다.

-_-;;; 왜 포인터가 아니라 참조자를 썼지; 잠깐 졸았나봅니다.

rlj1202의 이미지

const 는 상수인데, 포인터 변수 자체도 상수이지만 그 값을 참조하여 값을 바꾸는것 또한 금지 되는군요!
JavaVM 같은 경우 그 타입 자체로 포인터 변수인 것 이구요.

감사합니다!

익명 사용자의 이미지

일단 코드부터 보여드리죠.

int a = 0;	// int형 변수 두 개 정의
int b = 0;
 
		// OK.	라고 쓴 줄은 컴파일 잘 되고 실행도 잘 됩니다.
		// Err!	라고 쓴 줄은 컴파일 안 되는 잘못된 코드입니다.
 
int *p1 = a;	// 비상수를 가리키는 비상수 포인터
p1 = b;		// OK.	다른 객체를 가리키게 바꿀 수 있고
*p1 = 1;	// OK.	가리키는 객체의 값을 바꿀 수도 있습니다.
 
const int *p2 = a;	// 상수를 가리키는 비상수 포인터
p2 = b;			// OK.	다른 객체를 가리키게 바꿀 수는 있지만
*p2 = 2;		// Err!	가리키는 객체의 값을 바꿀 수는 없습니다.
b = 2;			// OK.	가리키고 있는 객체인 b는 사실은 비상수라서
			//	값을 바꿀 수 있지만
			// 	어쨌든 p2를 통해 바꿀 수는 없습니다.
 
int * const p3 = a;	// 비상수를 가리키는 상수 포인터
p3 = b;			// Err!	다른 객체를 가리키게 바꿀 수 없지만
*p3 = 3;		// OK.	가리키는 객체의 값을 바꿀 수는 있습니다.
 
const int * const p4 = a	// 상수를 가리키는 상수 포인터
p4 = b;				// Err!	다른 객체를 가리키게 바꿀 수도 없고
*p4 = 4;			// Err!	가리키는 객체의 값을 바꿀 수도 없습니다.

요점은, 포인터에 const를 적용할 때 "가리키는 것이 상수이거나", "포인터 자체가 상수이거나", 혹은 "둘 다거나"의 세 가지 경우가 있다는 말씀입니다.
어느 게 어느 경우인지 정확히 파악하려면, 휴먼 컴파일러가 되어야 합니다. 말인즉슨 C언어의 복잡하고 재귀적인 선언문 문법을 정확히 이해하고 적용할 줄 알아야 된다는 뜻이죠.

... 그런데, 솔직히 말씀드려서, C언어의 선언문 문법은 진짜 끔찍하게 복잡해요. 뭐 그만큼 유연하다는 의미이기도 한데, 언어 표준의 EBNF로 작성된 문법 정의를 읽는 데 어느 정도 익숙한 사람에게도 짜증을 유발시킬 정도입니다.
지금 저도 옆에 띄워놓고 보고 있는데, 대략 십여 페이지 정도의 해당 부분을 전부 발췌하지 않고는 도저히 설명을 못 드릴 거 같네요.
꼭 읽어보시겠다면 앞서 드린 드래프트( http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf ) 에서 6.7 Declarations, 6.7.3 Type qualifiers, 6.7.5 Declarators 위주로 읽어보세요.

그냥 간단히 (다소 모호하게) 정리해드리죠. * 오른쪽에 const가 있으면 그건 포인터 자체가 상수, 즉 포인터가 다른 걸 가리키게 만들 수 없다는 의미일 겁니다.
* 왼쪽에 있는 const는 포인터가 "가리키는 것"이 상수, 즉 포인터가 가리키고 있는 객체의 값을 변경할 수 없다는 의미일 겁니다.

이 정도 설명만으로도 많은 경우가 명확해질 거에요. 그리고 충분히 숙련되시기 전까지는, 이 설명 가지고는 이해할 수 없는 선언문은 가급적 안 만드시는 게 좋습니다. 아무리 const가 쓸 수 있는 한 많이 쓰는 게 좋다지만, 알고 써야죠.

익명 사용자의 이미지

int a = 0;	// int형 변수 두 개 정의
int b = 0;
 
		// OK.	라고 쓴 줄은 컴파일 잘 되고 실행도 잘 됩니다.
		// Err!	라고 쓴 줄은 컴파일 안 되는 잘못된 코드입니다.
 
int *p1 = &a;	// 비상수를 가리키는 비상수 포인터
p1 = &b;	// OK.	다른 객체를 가리키게 바꿀 수 있고
*p1 = 1;	// OK.	가리키는 객체의 값을 바꿀 수도 있습니다.
 
const int *p2 = &a;	// 상수를 가리키는 비상수 포인터
p2 = &b;		// OK.	다른 객체를 가리키게 바꿀 수는 있지만
*p2 = 2;		// Err!	가리키는 객체의 값을 바꿀 수는 없습니다.
b = 2;			// OK.	가리키고 있는 객체인 b는 사실은 비상수라서
			//	값을 바꿀 수 있지만
			// 	어쨌든 p2를 통해 바꿀 수는 없습니다.
 
int * const p3 = &a;	// 비상수를 가리키는 상수 포인터
p3 = &b;		// Err!	다른 객체를 가리키게 바꿀 수 없지만
*p3 = 3;		// OK.	가리키는 객체의 값을 바꿀 수는 있습니다.
 
const int * const p4 = &a	// 상수를 가리키는 상수 포인터
p4 = &b;			// Err!	다른 객체를 가리키게 바꿀 수도 없고
*p4 = 4;			// Err!	가리키는 객체의 값을 바꿀 수도 없습니다.

일부러 에러를 포함시키는 예제코드를 작성할 땐 컴파일러한테 검수받기가 무척 귀찮아서 보통 거르는데, 그러다보니 별 해괴한 실수를 고스란히 남기게 되는군요.
피곤해서 그런 것도 있는 거 같네요. 이거 올리고 잘 겁니다. -_-;; 다른 문제가 있더라도 적당히 직접 보완해가면서 읽어 주시길.

rlj1202의 이미지

헠....디테일한 설명 감사합니다.

댓글 달기

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
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.