코딩병아리
23
2019-04-09 23:43:42 작성 2019-04-10 00:28:16 수정됨
1
867

C언어 구조체 질문드립니다



#include <stdio.h>

struct Person {    // 구조체 정의

char name[20];        // 구조체 멤버 1
int age;                  // 구조체 멤버 3
char address[100];    // 구조체 멤버 3
};
int main()
{
struct Person p1;// 구조체 변수 선언
scanf("%s", p1.name);
printf("%s",p1);
return 0;
}
질문)p1.name에 값을 넣어주고 값을 보려면 p1.name을 출력해줘야된다는 걸 알고 있습니다.
근데 생각해보면 구조체는 서로 다른 자료형의 집합이고 이를 배열로 생각할 수 있는데,
배열의 이름은 주소를 나타냅니다.그럼 p1도 주소를 나타낼것이고 %s로 출력하면 출력이 되야되는데 정상적으로 출력되지않습니다.또 다른 경우로는 첫번째 요소가 name이 아닌 age가 오게 되면 p1으로 출력시 age의 값이 출력됩니다.왜 첫번째요소가 char name[20]이면 p1으로 출력이 안되는걸까요?
오히려 &p1으로 출력하면 정상출력이 되는데 첫번째 요소가 char name[20]일때,p1과 &p1의 차이는 무엇인가요?각각 어디를 가르키는지 알려주시면 감사하겠습니다.

질문2)첫번째 질문에서 scanf("%s",p1.name)을 통해 값을 넣어주고 int age와 char address[100]에 값을 안넣어준 상태에서 printf("%s",&p1);을 통해 출력하면 name의 값이 출력된다고 했는데 scanf("%d",p1.age);를 통해 age에도 값을 넣어준 상태에서 printf("%s",&p1);을 통해 출력하면 값이 출력되지 않습니다.어떠한 이유로 출력이 되고 안되는지 알고 싶습니다.

아직 배우는 입장이라 궁금한점이 많아 본의아니게 한 게시글에 두가지 질문을 쓰게됬습니다.죄송합니다 ㅠㅠ
0
  • 답변 1

  • MinorLife
    57
    2019-04-10 01:27:30 작성 2019-04-10 02:05:15 수정됨

    "... 구조체는 서로 다른 자료형의 집합이고 이를 배열로 생각할 수 있는데 ..."

    메모리 레이아웃 측면에서는 맞습니다. (나머지는 차차 알게 되실 거라 생각해서 패스...)
    그런데 문법적으로 C의 배열과는 다릅니다. 즉, 같지 않습니다.

    %s 는 어떻게 문자열을 출력해주는 걸까요? 우선 C 는 문자열 타입이라는 게 존재하지 않습니다.
    (많은 언어가 '있다.' 라고 하지만 대부분 완전 네이티브한 문자열 타입이라고 볼 수는 없습니다.)

    // 이 부분은 경험 및 지식이 저보다 훨~~씬 많으신 분들이 답변해주실 수 있을 것 같습니다.
    // 컴퓨터에서 문자열을 다루는 방법이 몇 가지가 있을텐데요.
    // 0 (==null) 을 만날 때까지를 전부 문자열로 보는 null-terminated string 이 있고, 가장 많이 쓰이지만.
    // 길이를 먼저 표기하고 후속 바이트들만 해석하는 방식도 있고,
    // 그 외에도 다양한 문자열 표기방법이 있다고 알고만 있습니다.

    어쨌든, %s 가 해주는 일이라는 건.. 

        const char* str = "Hello";
        int idx = 0;
    
        while ( str[idx] != NULL )
        {
            printf("%c", str[idx]);
            idx += 1;
        }
    
        return 0;

    대상 문자열 주소로부터 시작해 null 을 만날 때까지 (null-terminated string ; 널로 끝나는 문자열)
    를 '하나의 문자열' 로 보고 다루겠다는 겁니다. (위 코드는 아이디어 샘플 일 뿐입니다.)

    이걸 다른 식으로 증명해봅시다.

    #include <stdio.h>
    #include <string.h>
    
    //	__declspec.. 은 구조체의 패딩문제로,
    //	"1바이트를 기준으로 타이트하게 팩하길 바랍니다." 라는 C++ 문법입니다.
    //	C 에서는 MS 비주얼 스튜디오에서는 #pragma pack
    //	        gcc 에서는 다른 전처리 명령어가 있습니다.
    __declspec(align(1)) struct MyType {
    	char str[10];
    	char str2[10];
    };
    
    int main()
    {
    	MyType s;
    	strcpy_s( s.str,  "Hello" );
    	strcpy_s( s.str2, "World !" );
    
    	//	이 시점에서의 메모리 레이아웃은 ..
    	//	MyType{ str{ Hello00000 }, str2{ World !000 } } 일 것입니다.
    	s.str[5] = '_';
    	s.str[6] = '_';
    	s.str[7] = '_';
    	s.str[8] = '_';
    	s.str[9] = '_';
    
    	printf("sz:%d | %s", sizeof(MyType), s.str);
    	// 예상 출력결과 :
    	//	    sz:20 | Hello_____World !
    
    	printf(	"구조체 s 의 시작주소 : %p\n"
    			"배열 str 의 시작주소 : %p\n"
    			"배열요소 str[0] 의 시작주소 : %p\n"
    			, &s			// 구조체 s
    			, s.str			// 배열 str
    			, &(s.str[0])	// 배열요소 str[0]
    			);
    
    	return 0;
    }
    순수 C는 아니고 C++ 코드입니다.
    일부러 구조체의 멤버 str 과 str2 가 '연속된 메모리 레이아웃에 위치하게 하기 위해'
    구조체를 이용하였고, 패딩을 제거 하였습니다.
    이 때 출력결과는 주석부처럼 "Hello_____World !" 라고 나옵니다.

    이전 글로 미루어보건데 포인터/배열/구조체 등의 문법 이해가 부족하신 건 전혀 아닌 것 같습니다.
    디버거를 이용해서 직접 이런걸 들여다보시거나, 위와 같은 테스트코드를 작성해보시면
    평소에 재미있는 장난감처럼 가지고 노실 수 있을 것 같습니다! 화이팅!

    ---- ---- ---- ----

    정작 중요한 답변을 빼먹은 것 같습니다.

    1-1. 구조체는 배열이 아니다.
    1-2. 배열과 포인터는 비슷하지만, 차이점이 있다.
    1-3. Person 이 name 으로 시작하는 경우와 Person 이 age 로 시작하는 경우

    Person p;
    &p == &( p.name[0] ) == p.name 이기 때문입니다. 시작 주소가 모조리 같으니까요.

    vs

    Person p;

    &p == &( p.age ) 입니다. 단, 이 경우 '값' 은 같지만, 포인터로 해석되는 순간
    &p 가 해석하려는 대상의 크기는 Person 타입이고
    &(p.age) 가 해석하려는 대상의 크기는 int 타입입니다.

    그러므로 위에 예시로 드신 코드는 다음과 같이 쓰셔도 돌아갑니다.

    int main()
    {
        struct Person p1;
    
        scanf_s("%s", p1.name, 20);
        printf("%p %p %p\n", &p1, &(p1.name[0]), p1.name);//이 셋은 같습니다.
        printf("%s %s\n", p1.name, (char*)&p1);
        // 뒤의 캐스팅은, 다음 메모리에 대해 해석을 그렇게 해라! 라는 의미입니다.
    
        return 0;
    }

    그런데, 이게 된다고/안다고 해서 이렇게 하는 건 좋은 습관은 절대 아닌 것 같습니다.

    1-4. p1 은 그냥 구조체변수 p1 이고, &p1 은 구조체 변수의 주소입니다.

    2. 위에 적어둔 예시처럼 %s 가 다루는 방식이 %c 와 상이하며, %d 역시 상이하기 때문입니다.
    요컨데 format string (서식 문자열)을 다루는 함수의 format specifier (서식 지정자) 에 따라
    대상체를 어떻게 다루냐가 다르기 때문입니다.

    %c : "딱 1바이트를 문자로 보겠음."
    %d : "4바이트 까지의 범위를 정수로 보겠음."
    %s : "그 시작주소로부터 0(null) 을 만날 때까지 '널로 끝나는 문자열' 로 보겠음."

    그래서 더 큰 범위의 정수를 서식 문자열로 출력하기 위해서는 %ld, %lld 등을 이용해야합니다.

    ---- ---- ---- ----

    구조체 패딩 같은 키워드는 검색을 통해 대충 알아두시면 왜 전산학 기초들을 그렇게 강조하는 지
    공부하는 계기가 됩니다.

    제 경우에는 정도(正道)가 아니라 사도로 프로그래밍과 언어를 배웠는데,
    이런 부분을 조금 더 알았으면 좋겠다는 생각에 전산개론, 컴퓨터구조론 등의 학습을 하고 있습니다.

    이런 것들 궁금해 하시고, 재미있어 하시는 걸 보니 저랑 비슷한 과 이신 것 같습니다 ㅎㅎ

  • 로그인을 하시면 답변을 등록할 수 있습니다.