C언어 2차원 배열은 수식에서 연산시, 왜 이중 포인터처럼 하는 걸까요?

tmal의 이미지

C를 공부하고 있는 초보입니다. 공부하던 중, 2차원 배열에 대해서 한가지 의문이 들어서 질문 올립니다.

int str[2][3] = {1, 2, 3, 4, 5, 6}; 
 
**str = 9;
 
printf("%d", **str);

배열에 대한 배열은 수식에서 배열을 가리키는 포인터로 변경된다고 들었습니다. 하지만, 변경된 배열의 이름의 자료형은 (int**)이 아니라, (int*)인데, 어째서 이중 포인터 같은 연산을 할 수 있는건가요?

처음에는 str이 배열에 대한 배열이므로, str에 *연산을 하면, *str -> int[3]이 되고, 이것은 배열이니까, 한번 더 규칙에 의해 int [3]이 포인터로 변경되므로, 마치 이중 포인터 같은 연산을 할 수 있다고 생각했습니다.

그런데, 찾아보니 배열이 수식에서 포인터로 변경된다는 규칙은 "재귀적"으로 적용되는 규칙이 아니라고 하더군요.
그렇다면, 2차원 배열에서, *str까지는 포인터 연산이고, 그 후에 *연산은 그냥, 기존의 *연산과는 달리 적용된다는 걸까요? 궁금합니다.

익명 사용자의 이미지

step-by-step

str; // int [2][3]형, lvalue 배열
     // array-to-pointer decay로 인해 str[0]을 가리키는 int (*)[3]타입 rvalue 포인터로 변환
*str; // *(&str[0])와 같음. 즉 str[0]. 타입은 int [3], lvalue 배열
      // 또다시 array-to-pointer decay로 인해 str[0][0]을 가리키는 int *타입 rvalue 포인터로 변환
**str; // *(&str[0][0])와 같음. 즉 str[0], 타입은 int, lvalue.

따라서 **strstr[0][0]와 같은 객체를 나타내므로 값을 대입할 수도 있고(**str = 9) 읽을 수도 있는 것이지요.(printf("%d", **str);)

주목할 점이 하나 있습니다. **str를 평가하는 과정에서 int ** 타입은 한 번도 등장한 적이 없다는 것입니다. 예컨대 int[2][3] -> int (*)[3] -> int ** 같은 decay는 없다는 것이죠. 아마 '배열이 수식에서 포인터로 변경된다는 규칙은 "재귀적"으로 적용되는 규칙이 아니다' 라는 건 이런 의미에서 나온 말일 겁니다.

1차원 배열은 대개 포인터로 간주해도 되지만 (C언어에서 이 규칙의 예외는 대충 손으로 꼽힙니다) 2차원 배열을 이중 포인터로 간주해서는 안 되는 이유이기도 합니다. 상당히 많은 초보 프로그래머들이 이걸 헷갈려 합니다.

익명 사용자의 이미지

코드 정정

str; // int [2][3]형, lvalue 배열
     // array-to-pointer decay로 인해 str[0]을 가리키는 int (*)[3]타입 rvalue 포인터로 변환
*str; // *(&str[0])와 같음. 즉 str[0]. 타입은 int [3], lvalue 배열
      // 또다시 array-to-pointer decay로 인해 str[0][0]을 가리키는 int *타입 rvalue 포인터로 변환
**str; // *(&str[0][0])와 같음. 즉 str[0][0], 타입은 int, lvalue.
tmal의 이미지

str은 배열에 대한 배열이므로, *연산을 하면, int[3] 타입의 배열이 된다고 하셨습니다. 그럼, 배열 int[3]에도 배열의 이름이 있다는 건가요? 이 부분에서 헷갈립니다.

배열에 대한 배열 str[0]에는 배열 int[3]이 어떤 식으로 있길래, Ivalue 로 취급되는 건가요?

str을 연산할 때는 Ivalue인 배열 변수의 이름인 str이 있었으므로, 포인터로 변경될 수 있었다고 한다면, 배열 int[3]은 배열의 이름이 없을텐데, Ivalue 없이 어떻게 규칙이 적용되는지 의문입니다.

궁금해서, 제가 한번 추측을 해보았는데요. 제 추측은 이렇습니다.

→int (*)[3]은 배열 전체에 대한 포인터이므로, str[0]에 들어있는 배열 int[3]의 Ivalue는 int*3이라는 공간 자체(?)이다.

음.. 뭔가 말이 안되는 것 같네요. int[3]의 Ivalue는 무엇인가요??

익명 사용자의 이미지

1. T * 타입의 포인터 p에 대해 역참조 연산자 *를 적용했을 때,
1) p가 object를 가리키고 있었을 경우 *p는 해당 object에 대한 lvalue가 되며
2) *p의 type은 T가 됩니다.

2. int array[2][3]와 같은 배열이 있을 때, arrayint [3] 타입의 원소가 2개 있는 배열입니다. array-to-pointer decay가 일어나면 array는 배열의 첫 원소를 가리키는 non-lvalue pointer로 변환됩니다.

3. 다시 말해, 표현식 array가 decay되면 객체 array[0]을 가리키는 int (*)[3] 타입의 포인터가 됩니다.

4. 주어진 전체 표현식은 *array를 계산하고 있는데, 역참조 연산자 *는 decay에 대한 예외에 속하지 않습니다. 3의 decay된 결과에 1의 역참조 연산자 규칙을 대입해 보세요. *array는 객체 array[0]에 대한 int [3] 타입의 lvalue가 됩니다.

5. 이름이 있는지 없는지가 왜 중요하죠? *(char *)malloc(1)도 이름 없는 lvalue입니다.
1) 심지어 malloc(1)이 런타임에 평가될 때 NULL을 반환할 수도 있는데도 불구하고 그렇습니다. 다만 그런 경우엔 object를 가리키지 않는 lvalue라고 해서, undefined behavior의 원인이 되지요.

6. 네, 추측하신 내용이 대강 맞습니다. 표준에서 object란 "region of data storage in the execution environment, the contents of which can represent values" 으로 정의하고 있으니까요. 대충 러프하게 말하면, 메모리 상의 무언가를 나타내는 표현식이 있다면, 그게 지역/전역변수 이름이든 포인터 역참조이든 lvalue라고 말할 수 있습니다.

tmal의 이미지

int (*)[2]이런 식으로 쓰이면, 배열 전체를 가리키는 포인터가 되는데, 여기서 궁금한게 생겼습니다.
저는 규칙에 의해서, 배열의 Ivalue가 포인터로 변경되면, 자료형이 포인터인 주소값으로 바뀐다고 알고 있는데요.

int str[2][3];

그렇다면, str[0]의 주소값이 0x123라고 하고, 수식에서 str이 쓰인다면, (int*[3])(0x123)이 되는건가요?
배열 전체를 가리키는 포인터 변수 int (*p)[3]가 있다면, 이 변수의 자료형은 int*인가요? 아니면 int*[3]인가요?
캐스팅할때, (int*[3])은 올바르지 않다고 하는데요. 그렇다면, str이 자료형이 포인터인 주소값으로 변경된다고 하면,
(int*)(0x123)이 된다는 소린데, [3]이란 정보는 어떻게 가지고 있는 건가요?
익명 사용자의 이미지

여러 가지 오해가 있는 것 같군요.

Quote:
int (*)[2]이런 식으로 쓰이면, 배열 전체를 가리키는 포인터가 되는데

아뇨. int str[2][3]; 배열 전체를 가리키는 포인터, 즉 &str의 타입은 int (*)[2][3]입니다.

배열의 주소는 그 배열의 첫 원소의 주소와 같기 때문에, 보통 배열을 참조로 넘겨줄 땐 &str처럼 &를 쓰지 않고 그냥 이름을 넘기곤 하죠. 그러다보니 이런 종류의 오해가 생기는 것 같네요.

T에 대한 포인터 타입은 T *. int [2][3]에 대한 포인터 타입은 int (*)[2][3] 이 되는 것이지요.

Quote:
저는 규칙에 의해서, 배열의 Ivalue가 포인터로 변경되면, 자료형이 포인터인 주소값으로 바뀐다고 알고 있는데요.

네, 대강 맞습니다.

Quote:
그렇다면, str[0]의 주소값이 0x123라고 하고, 수식에서 str이 쓰인다면, (int*[3])(0x123)이 되는건가요?

아뇨. 사람들끼리는 대강대강 말할 수 있어도, 컴퓨터 언어를 쓸 때는 정확하게 해야죠. (int*[3])(0x123)가 아니라 (int(*)[3])(0x123) 입니다.

이 문제는 int*[3]이 "int 3개짜리 배열에 대한 포인터" 혹은 "int* 3개짜리 배열", 두 가지 방식으로 해석될 수 있어서 생기는 문제입니다. *를 괄호로 감싸지 않으면, [3]이 먼저 달라붙으면서 후자의 경우로 해석이 됩니다. 우리는 전자를 나타내고 싶기 때문에 *를 괄호로 감싸서 (int(*)[3])(0x123)와 같이 써 줘야 하는 것이죠.

보통 배열에 대한 포인터를 사용해야 하는 경우가 잘 없어서 사람들이 잘 모르는데, 좀 더 유명한 사례로는 함수 포인터를 다루는 경우에도 비슷한 문제가 있어서 타입 선언을 매우 읽기 짜증나게 만들곤 합니다.

Quote:
배열 전체를 가리키는 포인터 변수 int (*p)[3]가 있다몈, 이 변수의 자료형은 int*인가요? 아니면 int*[3]인가요?

아까도 말씀드렸지만 위의 p는 엄밀히 따지면 배열 전체를 가리키는 포인터가 아닙니다. 물론 C언어에서 배열의 첫 원소를 가리키는 포인터를 배열처럼 쓰는 경우가 워낙 많기 때문에 딱 잘라 말씀드리기도 애매한 부분입니다만.

배열 전체를 가리키는 포인터 타입은 int(*)[2][3], 첫 원소를 가리키는 포인터 타입은 int(*)[3]

Quote:
캐스팅할때, (int*[3])은 올바르지 않다고 하는데요. 그렇다면, str이 자료형이 포인터인 주소값으로 변경된다고 하면,

그게 올바르지 않은 이유는 아까 말씀드렸듯 int*[3]이 "int* 3개짜리 배열"로 해석되기 때문입니다. C언어에서 타입 캐스트는 scalar type에 대해서만 가능합니다.

Quote:
(int*)(0x123)이 된다는 소린데, [3]이란 정보는 어떻게 가지고 있는 건가요?

(int (*)[3])(0x123)라니까요. [3]이란 정보는, 컴파일러가 컴파일 하는 동안 같이 들고 있습니다.

tmal의 이미지

계속 헷갈렸던게, 이제 좀 정리가 되는 거 같네요. 감사합니다.~!!

댓글 달기

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