배움 저장소

[홍정모의 따라하며 배우는 C언어] 14. 구조체 본문

Programming Language/C

[홍정모의 따라하며 배우는 C언어] 14. 구조체

시옷지읏 2021. 12. 5. 21:47

14.1 구조체가 필요한 이유

1. 여러 자료형 배열을 Index base로 관리하기 어렵다

2. 구조체를 이용하여 Tag 하나로 관리할 수 있다

- 구조체 내부 member variable에 접근하기 위하여 Dot(.) Operator를 사용한다.

- 메모리를 효율적으로 사용하기 위해 멤버 member variable 사이에 빈공간(padding)을 둔다.

 

14.2 구조체의 기본적인 사용법

#define MAX 41

struct person // person is a tag of structure
{
	char name[MAX];// member
	int age;       // member
	float height;  // member
};

int main()
{
	int flag; // Receives return value of scanf()

	/* Structure variable */
	struct person genie;

	/* dot(.) is structure membership operator (member access operator, member operator) */
	strcpy(genie.name, "Will Smith"); //strncpy(genie.name, "Kim Genie", MAX);
	genie.age = 20;

	/* dot(.) has higher precedence than & */
	flag = scanf("%f", &genie.height);
	printf("height:%f \n", genie.height);


	/* Initialization  */
	struct person princess = { // follow the declaration order
		"Naomi Scott",
		18,
		160.0f
	};
	// struct person princess = { "Naomi Scott", 18, 160.0f }; 


	/* Designated Initializers */
	struct person beauty = { // insert variable on exact declaration
		.age = 19,           // Don't have to follw declaration order
		.name = "Bell",
		.height = 150.f
	};
	// struct person beauty = { .age = 19, .name = "Bell", .height = 150.f };


	/* Pointer to a struct variable */
	struct person* someone;
	someone = &genie;
	//someone = (struct person*)malloc(sizeof(struct person)); // free later

	/* Indirect member(ship) operator (or structure pointer operator) 
	   arrow(->) operator usage : "pointer identifier" -> "member variable"  */
	someone->age = 99;
	(*someone).height = 155.f;
}

 

 

함수 안에서 구조체 선언하기

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

#define MAX 30

void printBookInfo()
{
    /* Structure declarations in a function */
    struct book
    {
        char title[MAX];
        float price;
    };
    struct book book_one = { "The History of Korea", 1.5f };
    printf("%s %f\n", book_one.title, book_one.price);
}

int main()
{
    printBookInfo();
}

 

임시 구조체 선언 후 구조체 변수 선언하기

/* No tag, temporary struct use only once, Initializing such variable */
struct
{
    char farm[MAX];
    float price;
} apple, grape; // initialzed!

apple.price = 1.2f;
strcpy(apple.farm, "DaeGu");

grape.price = 1.0f;
strcpy(grape.farm, "DaeGu");

 

Typedef 사용하기

  간결하게 구조체 변수를 선언할 수 있다

/* typedef makes 'structure keyword' be short */
typedef struct person persontype; // replace (struct person) to (persontype)
persontype pt;

typedef struct person person; // much cleaner
person ps;

typedef struct {
    char name[MAX];
    int age;
}friend;

friend f1 = { "Jay", 15 };
friend f2 = { "Joe", 15 };

printf("f1 %s %i \n", f1.name, f1.age);
printf("f2 %s %i \n", f2.name, f2.age);

 

14.3 구조체의 메모리 할당 (Memory Allocation)

정렬이 잘 되어 패딩이 없는 구조체

/* Well alligned structure */
struct Aligned
{                  // If declare struct's variable
    int i;         // | 0 1 2 3 | 4 5 6 7 | 8 9 10 11 12 13 14 15 | 
    float f;       // |  int i  | float f |      double d         | 
    double d;      //  4 + 4 + 8 = 16 byte    
};

struct Aligned aligned;
printf("Struct Aligned a1\n"); // Struct Aligned a1
printf("Sizeof %zd\n", sizeof(struct Aligned)); // Sizeof 16
printf("%p \n", &aligned);       // 00F3F760
printf("%p \n", &aligned.i);     // 00F3F760
printf("%p \n", &aligned.f);     // 00F3F764
printf("%p \n", &aligned.d);     // 00F3F768

 

메모리 패딩이 필요한 구조체

  1. 주소를 표현하는 최소단위는 8bit = 1byte이다.

  2. CPU - RAM이 정보를 주고 받을 때 최소 단위는 4byte(32bit system)이다.

     이 때 최소단위를 word라 부른다.

/* padding (struct member alignment) - 1 word : 4bytes in x86, 8bytes in x64 */
struct Padded1
{                  // without padding
    char c;        // | 0 | 1 2 3 4 | 5 6 7 8 9 10 11 12 |  
    float f;       // | c |  float  |       double       | 
    double d;      //  1 + 4 + 8 = 13           
};

struct Padded1 paded1;
printf("\nStruct Padded1 \n");
printf("Sizeof %zd\n", sizeof(paded1)); // Sizeof 16
printf("%p \n", &paded1);       // 00D3F7C4
printf("%p \n", &paded1.c);     // 00D3F7C4
printf("%p \n", &paded1.f);     // 00D3F7C8
printf("%p \n", &paded1.d);     // 00D3F7CC

/* added padding
  | 0 1 2 3 | 4 5 6 7 | 8 9 10 11 12 13 14 15|  
  | char(?) |   float |        double        | 
      ^ add padding  4 + 4 + 8 = 16
*/

  만약 패딩없이 word를 보낸다고 가정하자. 첫 word는 char과 float를 섞어서 보낸다.

  두 번째 word는 짤린 float와 double의 앞부분을 잘라서 보낸다.

  세 번째 word는 double의 뒷부분과 비어있거나 쓰레기값을 보낼 것이다.

비어있는 뒷 부분을 앞쪽으로 당겨 사용하여도 괜찮지 않을까? 그러면 패딩이 된다.

 

위에서 구조체 member variable 순서를 바꾸면 어떻게될까? 아래 글을 보자.

 

정렬이 잘못 되어 메모리를 낭비하고 있는 구조체

  위 Padded1과 동일한 변수를 담고있지만 순서가 다르다. Padded1은 16byte, Padded2는 24byte로 8byte를 낭비한다

struct Padded2
{                 // without padding
    float f;      // | 0 1 2 3 | 4 5 6 7 8 9 10 11 |  12  |  
    double d;     // |  float  |       double      | char | 
    char c;       //  4 + 8 + 1 = 13           
};

struct Padded2 paded2;
printf("\nStruct Padded2 \n");
printf("Sizeof %zd\n", sizeof(paded2)); // Sizeof 24
printf("%p \n", &paded2);       // 00B5FAF8
printf("%p \n", &paded2.f);     // 00B5FAF8
printf("%p \n", &paded2.d);     // 00B5FB00
printf("%p \n", &paded2.c);     // 00B5FB08

// "Bad Alligned"
// | 0 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15| 16 17 18 19 20 21 22 23 |
// |     float f     |      double d        |         char c          |

 

문자열도 똑같이 적용할 수 있다

  문자열 개수를 16에서 17로 바꾸어보자. 구조체의 크기가 8byte 증가한다.

	struct Person
	{                 
		char name[16];
		double d;     
		float f;      
	};

	struct Person person;
	printf("\nPerson \n");
	printf("Sizeof %zd\n", sizeof(person));//  Size 40  =>  Size 32
	printf("%p \n", &person);              // name[17]  => name[16]   
	printf("%p \n", &person.name);         // 00E5FD10  => 00F3FC30
	printf("%p \n", &person.d);            // 00E5FD28  => 00F3FC40
	printf("%p \n", &person.f);            // 00E5FD30  => 00F3FC48

 

구조체 배열

struct Person Group[3];
printf("Sizeof a structure Array : %zd \n", sizeof(Group));

>> Sizeof a structure Array : 96

 

구조체 패딩 기준 설정하기

  1byte로 바꾸면 패딩이 없어진다.

 

x86과 x64에서 패딩의 크기가 같은 이유

더보기
더보기

메모리 정렬(Memory Align)

1. 구조체 메모리 정렬 사용하는 패딩의 크기는 멤버 변수의 자료형이 결정한다

   워드 사이즈가 아니다. 따라서 운영체제가 아닌 컴파일러가 결정한다.

2. 변수의 주소가 자료형의 배수를 일 때 CPU가 접근하기 좋다.

 - char는 1의 배수, int 와 float은 4의 배수, double은 8의 배수의 메모리 주소를 가진다. 

 - char과 char[ ]은 어떤 메모리 주소도 사용할 수 있다.

 - 해당 배수가 아니라면 패딩이후 해당 배수의 메모리 주소에서부터 object를 사용할 것이다.

3. padding rule은 구조체 멤버 변수 중 가장 큰 자료형을 따른다.

https://stackoverflow.com/questions/4306186/structure-padding-and-packing

 

구조체의 주소

1. 64bit system에서 구조체 주소는 16byte 배수를 사용한다. (제일 큰 자료형-long double이 16byte)

2. 64bit system이라도 구조체가 char 자료형만 담고 있다면 어떤 주소라도 사용 가능하다.

3. 구조체 사이 padding으로 사용된 저장공간을 활용할 수 있다. 컴파일러가 해준다. 

 

14.4 구조체의 배열 연습문제

#define MAX_TITLE 16
#define MAX_AUTHOR 16
#define MAX_BOOKS 3

struct book
{
    char title[MAX_TITLE];
    char author[MAX_AUTHOR];
    int price;
};

char* s_gets(char* str, int n)
{
    char* result;
    char* find;

    result = fgets(str, n, stdin); // scanf( ) save data at every space or newline
    if (result != NULL)
    {
        find = strchr(str, '\n');  // search newline
        if (find)                  // if find
            *find = '\0';          // replace character to null
        else
            while (1)              // there is no newline character in conlsole input
            {                      // maybe buffer has rest of character and newline
                char ch = getchar();
                if (ch != '\n')
                {
                    printf("dispose! %c \n", ch); // discharge rest of buffer
                    continue;
                }
                else
                    break;                        // catch newline!
            }
    }
    return result;
}

int main()
{
    struct book books[MAX_BOOKS] = { {"\0","\0",0}, };
    int count = 0;

    while (1)
    {
        printf("Input a book title or press [Enter] to stop.\n>>");
        if (s_gets(books[count].title, MAX_TITLE) == NULL) break; // Helped by lecturer to use if
        if (books[count].title[0] == '\0') break; // s_gets convert \n to \0


        printf("Input the author.\n>>");
        s_gets(books[count].author, MAX_TITLE);

        printf("Input the price.\n>>");
        int flag = scanf("%i", &(books[count].price));  // Helped by lecturer to use flag
        while (getchar() != '\n') // scanf( ) needs clearing buffer
            continue;

        count++;

        if (count == MAX_BOOKS)
        {
            printf("Books are enough...\n>>");
            break;
        }
    }

    if (0 < count)
    {
        printf("\n Book list\n");
        for (int i = 0; i < count; ++i)
        {
            printf("title:%s \
\n\t\t written by %s\
\n\t\t price : %i \n", books[i].title, books[i].author, books[i].price);
        }
    }
    else
        printf("No books to show\n");
}

 

14.5 구조체를 다른 구조체의 멤버로 사용하기(Nested Structures)

#define LEN 20

struct names
{
	char given[LEN]; // First Name
	char family[LEN];// Last Name
};
struct reservation
{
	struct names guest; // a Nested Structure
	struct names host;  //
	char food[LEN];
	char place[LEN];

	//time
	int year;
	int month;
	int day;
	int hour;
	int minutes;
};

int main(void)
{
	// Use Designated Initialization
	struct reservation res = {
		.guest   = {"Nick", "Carraway"},
		.host    = {"Jay",  "Gatsby"},
		.place   = {"the Gatsby mansion"},
		.food    = {"Pizza"},
		.year    = 1925,
		.month   = 4,
		.day     = 10,
		.hour    = 18,
		.minutes = 30
	};
	//  Complete letter with above structure
	/*
		Dear Nick Carraway,
		I would like to serve you Pizza.
		Please visit the Gatsby mansion on 10/4/1925 at 18:30.
		Sincerely,
		Jay Gatsby
	*/
	const char* formatted =
"Dear %s %s, \n\
I would like to serve you %s.\n\
Please visit %s on %i/%i/%i at %i:%i.\n\
Sincerely, \n\
%s %s\n";

	printf(formatted,
	res.guest.given, res.guest.family,
	res.food,
	res.place , res.day, res.month, res.year, res.hour, res.minutes,
	res.host.given, res.host.family);
}

 

14.6 구조체와 포인터

구조체 포인터 사용하기

#define LEN 20

struct names {
	char given[LEN];
	char family[LEN];
};

struct friend{
	struct names full_name;
	char mobile[LEN];
};

int main(void)
{
	struct friend friends[2] = {
		{ {"Ariana", "Grande"},  "1234-1234" },
		{ { "Taylor", "Swift" }, "6556-6556" }
	};
	
	struct friend* best_friend;
	best_friend = &friends[0];

	printf("%zd \n", sizeof(struct friend));
	printf("%p %s \n", best_friend, best_friend->full_name.given);

	best_friend++;
	printf("%p %s \n", best_friend, (*best_friend).full_name.given);
}

 

대입연산자 활용하기

 Q. 멤버 변수가 배열일 때 대입연산자는 어떻게 작동할까? 배열의 주소를 그대로 넘겨줄까?

 A. 멤버 변수가 배열일 때 대입연산자는 새로운 저장공간을 만든다.

struct data
{
	int i;
	char c;
	float arr[2];
};

int main(void)
{
	struct data d1 = { 111, 'A', }; // OR { 111, 'A', {1.1f, 2.2f} };
	d1.arr[0] = 1.1f;
	d1.arr[1] = 2.2f;

	printf("%d %c %p \n", d1.i, d1.c, d1.arr);    // >> 111 A 010FFE50
	printf("%f %f \n", d1.arr[0], d1.arr[1]);
	printf("%p %p \n\n", &d1.arr[0], &d1.arr[1]); // >> 010FFE50 010FFE54

	struct data d2 = d1; //  assignment operator
	printf("%d %c %p \n", d2.i, d2.c, d2.arr);    // >> 111 A 010FFE38
	printf("%f %f \n", d2.arr[0], d2.arr[1]);
	printf("%p %p \n\n", &d2.arr[0], &d2.arr[1]); // >> 010FFE38 010FFE3C
}

 

구조체 맴버가 포인터일 때 대입연산자는 기존의 저장공간을 그대로 사용한다.

struct data
{
	int i;
	char c;
	float* arr;
};

int main(void)
{
	struct data d1 = { 111, 'A', NULL };
	d1.arr = (float*)malloc(sizeof(float) * 2);
	d1.arr[0] = 1.1f;
	d1.arr[1] = 2.2f;

	printf("%d %c %p \n", d1.i, d1.c, d1.arr);    // >> 111 A 00E74E60
	printf("%f %f \n", d1.arr[0], d1.arr[1]);
	printf("%p %p \n\n", &d1.arr[0], &d1.arr[1]); // >> 00E74E60 00E74E64

	struct data d2 = d1; //  assignment operator  
	printf("%d %c %p \n", d2.i, d2.c, d2.arr);    // >> 111 A 00E74E60
	printf("%f %f \n", d2.arr[0], d2.arr[1]);
	printf("%p %p \n\n", &d2.arr[0], &d2.arr[1]); // >> 00E74E60 00E74E64
}

 

14.7 구조체를 함수로 전달하는 방법

#define LEN 50
struct fortune
{
    char bank_name[LEN];
    double bank_saving;
    char fund_name[LEN];
    double fund_invest;
};

double sum(const struct fortune *my_fortune); // <- double sum(double*, double*)

int main()
{
    struct fortune my_fortune =
    {
        .bank_name = "Wells-Fargo",
        .bank_saving = 4032.273,
        .fund_name = "JPMorgan Chase",
        .fund_invest = 8543.946
    };

    printf("Total : $%.2f\n", sum(&my_fortune));
    //printf("Total : $%.2f\n", sum(&my_fortune.bank_saving, &my_fortune.fund_invest));
    return 0;
}

double sum(const struct fortune *my_fortune)
{
    //return (*my_fortune).bank_saving + (*my_fortune).fund_invest;
    return my_fortune->bank_saving + my_fortune->fund_invest;
}

함수 파라미터가 call by value일 때 문제점  1. 메모리 낭비 (같은 변수 값이 복사된다, 크기가 거대하면 연산이 느리다)

                                                        2. 동적할당된 변수는 복사되지 않고 주소값을 그대로 가져옴

따라서 pointer를 사용해서 주소 값을 전달하자.

 

14.8 구조체와 함수 연습문제

객체지향 프로그램의 Object = 구조체(다른 타입의 변수들) + 구조체 멤버 변수를 사용하는 함수

 

포인터를 활용하여 이름 입출력하기

#define LEN 30

typedef struct
{
    char first[LEN];
    char last[LEN];
    int num;
}Name_count;

void receive_input(Name_count*);
void count_characters(Name_count*);
void show_result(const Name_count*);
char* s_gets(char* st, int n);

int main()
{
    Name_count user_name;
    receive_input(&user_name);
    count_characters(&user_name);

    show_result(&user_name); // no needs to type const

    return 0;
}


//void receive_input(Name_count* user)
//{
//    printf("Insert your first name!\n>>");
//    s_gets(user->first, LEN);
//
//    printf("Insert your last name!\n>>");
//    s_gets(user->last, LEN);
//}
void receive_input(Name_count* user)
{
    int flag; 
    printf("Input your first name \n>>");
    flag = scanf("%s", user->first); // %[^\n] means read str

    //flag = scanf("%[^\n]%*c", user->first); // %[^\n] means read str
    if (flag != 1)                          // until reached to \n
        printf("Wrong input");              // %*c means delete this on buffer

    printf("Input your Last name \n>>");
    flag = scanf("%s", user->last);
    //flag = scanf("%[^\n]%*c", user->last);
    if (flag != 1)
        printf("Wrong input");
}

//void count_characters(Name_count* user)
//{
//    int count = 0;
//    char* ptr = user->first;
//    while (*ptr != '\0')
//    {
//        count++;
//        ptr++;
//    }
//    ptr = user->last;
//    while (*ptr != '\0')
//    {
//        count++;
//        ptr++;
//    }
//    user->num = count;
//}
void count_characters(Name_count* user)
{
    user->num = strlen(user->first) + strlen(user->last);
}

void show_result(const Name_count* user)
{
    printf("your name is %s %s has %i characters\n", user->last, user->first, user->num);
}

//char* s_gets(char* st, int n)
//{
//    char* start = st;
//    char ch;
//    while ((ch = fgetc(stdin)) != 0 && ch!='\n')
//    {
//        *st = ch;
//        st++;
//    }
//    *st = '\0';
//    return start;
//}

char* s_gets(char* str, int n)
{
    char* start = str;
    char* ptr = NULL;
    
    fgets(str, LEN, stdin);
    if (start != NULL)
    {
        ptr = strchr(start, '\n');
       if(ptr)
        *ptr = '\0';
       else
       {
           while (getc(stdin) != '\n')
               continue;
       }
    }
}

 

대입연산자를 활용하여 이름 입출력하기

  strcpy를 사용하지 않고 대입 연산자로 해당 값을 바꾸어줄 수 있다. 새로운 구조체 변수를 만들어 대입해주면 된다.

#define LEN 30

typedef struct
{
    char first[LEN];
    char last[LEN];
    int num;
}Name_count;

Name_count receive_input();
Name_count count_characters(Name_count);
void show_result(const Name_count);
char* s_gets(char* st, int n);

int main()
{
    Name_count user_name;

    user_name = receive_input();

    user_name = count_characters(user_name);

    show_result(user_name); // no needs to type const

    return 0;
}

Name_count receive_input()
{
    char* ptr=NULL;
    Name_count user;

    printf("Input your last name\n>>");
    s_gets(user.last, LEN);

    printf("Input your first name\n>>");
    s_gets(user.first, LEN);

    return user;
}

Name_count count_characters(Name_count user)
{
    user.num = strlen(user.first) + strlen(user.last);
    printf("\n num : %i \n", user.num);
    return user;
}

void show_result(const Name_count user)
{
    printf("your name is %s %s has %i characters\n", user.last, user.first, user.num);
}

char* s_gets(char* st, int n)
{
    char* start = st;
    char ch;
    while ((ch = fgetc(stdin)) != 0 && ch!='\n')
    {
        *st = ch;
        st++;
    }
    *st = '\0';
    return start;
}

 

scanf( ) 함수에서 사용하는 format specifier

  %[^\n]%*c: \n가 나올때까지 문자를 읽는다. 이 때 \n는 buffer에 남아있다. 이를 지워주기 위하여 %*c를 사용하자.

  다음 코드는 %*c와 있을 때와 없을 때를 비교하는 코드이다

char test[10];
// int flag = scanf("%[^\n]%*c", test); // use %*c for deleting buffer 
int flag = scanf("%[^\n]", test);      

for (int i = 0; i < 10; ++i)
{
    printf("%3i %c \n", (int)test[i], test[i]);
}

char ch;

while ((ch = getc(stdin))!= NULL)
{
    printf("ommitting %c\n",ch);
    continue;
}

 

14.9 구조체와 할당 메모리

동적 할당의 잘못된 예

  fname과 lname이 Text segment에 저장되어있다. scanf 함수로 해당 주소에 있는 값을 바꾸려고 하면 에러가 발생한다.

struct namect {
    char* fname; // Use malloc( )
    char* lname; // Use malloc( )
    int letters;
};

struct namect p = { "John", "King" };
printf("%s %s\n", p.fname, p.lname);       

int flag = scanf("%[^\n]%*c", p.lname);
printf("%s %s\n", p.fname, p.lname);

 

동적 할당의 적절한 예

  buffer를 만들어 활용하면 동적 할당을 사용할 수 있다.

char buffer[LEN] = { 0, };
int flag = scanf("%[^\n]%*c", buffer);
p.fname = (char*)malloc(strlen(buffer) + 1);

if (p.fname != NULL)
    strcpy(p.fname, buffer);
printf("%s %s\n", p.fname, p.lname);

 

동적할당으로 이름 입출력하기

  1. buffer에 입력값을 저장한 후, 메모리를 할당한다.

  2. 해당 메모리에 strcpy( )함수를 사용하여 값을 복사하여 넣는다. 

#define LEN 30

struct namect {
	char* fname; // Use malloc( )
	char* lname; // Use malloc( )
	int letters;
};

void getinfo(struct namect*);           // allocate memory
void makeinfo(struct namect*);        
void showinfo(const struct namect*);
void cleanup(struct namect*);           // free memory when done

int main()
{
	struct namect temp = { NULL, NULL, 0 };
	struct namect* user = &temp;
	getinfo(user);
	makeinfo(user);
	showinfo(user);
	cleanup(user);
}

void getinfo(struct namect* user)
{
	printf("Input your first name\n>>");

	char buffer[LEN] = { 0, };
	int flag = scanf("%[^\n]%*c", buffer);
	if (flag != 1) { // flag is counting how many input is there. 
		printf("Bad input!\n");
		exit(1);
	}

	user->fname = (char*)malloc(strlen(buffer)+ 1); // add 1 for '\0'
	if (user->fname == NULL)  //sizeof char = 0
	{
		printf("Impossible to allocate memory!");
		exit(1);
	}
	strcpy(user->fname, buffer);

	printf("Input your last name\n>>");
	flag = scanf("%[^\n]%*c", buffer);
	user->lname = (char*)malloc(strlen(buffer) + 1);
	if (!user->lname)
	{
		printf("Impossible to allocate memory!");
		exit(1);
	}
	strcpy(user->lname, buffer);
}

void makeinfo(struct namect* user)
{
	user->letters = strlen(user->fname) + strlen(user->lname);
}
void showinfo(const struct namect* user)
{
	printf("Your name is %s %s has %i characters\n", user->fname, user->lname, user->letters);
}
void cleanup(struct namect* user)
{
	free(user->fname);
	free(user->lname);
	printf("cleaned up memory!\n");
}

 

14.10 복합 리터럴

 한 번 초기화 한 구조체는 변수를 한꺼번에 바꿀 수 없다. 초기화 할 때처럼 assignment operator = { .. }가 불가능하다.

 이 때 여러 리터럴 자료형을 합친 복합 리터럴 을 사용하여 임시 구조체를 만든 후 초기화된 구조체에 값을 할당할 수 있다.

struct rectangle
{
	int width;
	int height;
};
int rect_area_callbyval(struct rectangle r)
{
	return r.width * r.height;
}
int rect_area_callbyref(struct rectangle* r)
{
	return r->width * r->height;
}

int main()
{
    /* Bad Usage */
	struct rectangle rectangle1 = { 3,3 };
	//rectangle1 = { 5,5 }; // Error! Only possible in initialization
	
	/* Assign value on each member variable */
	rectangle1.width = 5;
	rectangle1.height = 5;

	/* Use Assignment Operator */
	struct rectangle temp_r = { 10,10 };
	rectangle1 = temp_r;
	rectangle1 = (struct rectangle){ 15, 15 };
	
	/* use temporary struct variable on argument */
	rect_area_callbyval((struct rectangle) { 20, 20 });
	rect_area_callbyref(&(struct rectangle) { .height = 25, .width = 25 });
}

 

14.11 신축성있는 배열 멤버

 구조체 내에 정의되는 배열을 동적으로 사용할 수 있다. 포인터가 아니다 배열의 공간을 동적으로 할당할 수 있다. 이 때 배열인 멤버변수는 반드시 마지막 순서에 선언되어라. 구조체 크기를 할당 받을 때 배열의 크기를 고려하여 더해주면 된다.

동적할당 포인터 vs 배열

1. flexible array는 배열이기 때문에 포인터와 다르다. 자기 자신이 별도의 저장공간을 가지는 포인터와 달리 배열의

  식별자가 가리키는 주소는 배열 첫 값이다. 때문에 메모리를 아낄 수 있다. 포인터 메모리(4byte)를 아낄 수 있다.

  아래 코드에서 struct 내부에서 flex.values의 sizeof를 확인해보면 0byte임을 확인할 수 있다.

2. 동적할당된 메모리는 힙에서 어느 위치에 저장되었는지 알 수 없다.

   배열은 이전 멤버변수에 이어진 주소값을 가진다.(구조체와 일 열로 나열된 메모리 주소를 가진다)

struct flex
{
    size_t count;
    double average;
    double values[];	// flexible array should be last member variable
};

/*struct flex
{
    size_t count;
    double average;
    double* values; // Dynamic allocation. pointer has own's data
};*/

const size_t n = 3;
struct flex* s_ptr = (struct flex*)malloc(sizeof(struct flex) + n * sizeof(double));
if (s_ptr == NULL) exit(1);

s_ptr->count = n;
for (size_t i = 0; i < s_ptr->count; ++i)
    s_ptr->values[i] = 1.1 * i;

s_ptr->average = 0.0;
for (unsigned i = 0; i < s_ptr->count; ++i)
    s_ptr->average += s_ptr->values[i];
s_ptr->average /= (double)s_ptr->count;


printf("Flexible Array member \n");
printf("Sizeof struct flex  %zd\n", sizeof(struct flex));
printf("Sizeof *s_ptr       %zd\n", sizeof(*s_ptr));
printf("Sizeof malloc       %zd\n", sizeof(struct flex) + n * sizeof(double));

printf("struct ptr      add %p\n", s_ptr);
printf("     count      add %p\n", &s_ptr->count);
printf("     count     size %zd\n",sizeof(s_ptr->count));
printf("s_ptr->average  add %p\n", &s_ptr->average);
printf("s_ptr->values   add %p\n", &s_ptr->values);
printf("s_ptr->values   val %p\n", s_ptr->values);
printf("s_ptr->values  size %zd\n", sizeof(s_ptr->values));

Output

Flexible Array member
Sizeof struct flex  16
Sizeof *s_ptr       16
Sizeof malloc       40
struct ptr      add 00E64D40
     count      add 00E64D40
     count     size 4
s_ptr->average  add 00E64D48
s_ptr->values   add 00E64D50
s_ptr->values   val 00E64D50
s_ptr->values  size 0

1. size_t count와 double average의 주소값 차이는 8이다. size_t의 크기는 4이다. padding이 되었음을 확인할 수 있다.

2. struct와 *s_ptr의 sizeof는 모두 16이다. 배열까지 합하면 40byte이어야 한다. 구조체 크기에 배열 값이 포함되지

   않았음을 확인할 수 있다. 동적할당을 받았기 때문에 컴파일러는 배열의 크기를 알 수 없다. 이 때문에 포인터를

   dereference 후 대입연산자를 사용하면 16byte만 복사하여 할당한다. memcpy( )함수를 사용하면 모두 복사할 수 있다

 

다음 코드를 실행하면 new_ptr에 있는 배열에 쓰레기값이 들어있음을 확인할 수 있다

	struct flex* new_ptr = (struct flex*)malloc(sizeof(struct flex) + n * sizeof(double));
	if (new_ptr == NULL) exit(1);

	*new_ptr = *s_ptr;

	for (size_t i = 0; i < s_ptr->count; ++i)
		printf("%f ", s_ptr->values[i]);
	printf("\n");

	for (size_t i = 0; i < new_ptr->count; ++i)
		printf("%f ", new_ptr->values[i]);

Output

0.000000 1.100000 2.200000
-6277438562204192487878988888393020692503707483087375482269988814848.000000 -6277438562204192487878988888393020692503707483087375482269988814848.000000 -6277438562204192487878988888393020692503707483087375482269988814848.000000

따라서 대입연산자를 활용하기 보다. memcpy( ) 을 사용해주자.

 

 

14.12 익명 구조체

Nested structure의 사용예제

struct names // tag is names
{
	char first[20];
	char last[20];
};
struct person // tag is person
{
	int id;
	struct names name; // nested structure member
};

int main()
{
	struct person CEO = {
		.id = 100,
		{"Bill", "Gates"}
	};

	printf("%i\n",CEO.id);
	puts(CEO.name.first);
	puts(CEO.name.last);
}

 

Nested Structure를 사용하는 대신 Anonymous(Tempolary) Structure를 사용할 수 있다

  장점

    1. Nested Struct를 위해 struct를 따로 정의하지 않는다.

    2. 멤버 변수를 호출하기 위해 Tag를 한 번만 사용하면 된다.

struct person_simple // tag is person_simple
{
	int id;
	struct { char first[20]; char last[20]; }; // anonymous structure
};

int main() {

	struct person_simple CEO = {
		.id = 111,
		{"Steve", "Jobs"}
	};

	printf("%i\n",CEO.id);
	puts(CEO.first);
	puts(CEO.last);
}

 

14.13 구조체의 배열을 사용하는 함수

구조체 배열

#define LEN 30

struct book
{
	char name[LEN];
	char author[LEN];
};

void print_books(const struct book books[], int n);

int main() {
	struct book my_books[3]; // = { { "The Great Gatsby", "F. Scott Fitzegrald" }, { ... }, ... };
	my_books[0] = (struct book){ "The Great Gatsby", "F. Scott Fitzegrald" };
	my_books[1] = (struct book){ "Hamlet", "William Shakespear"            };
	my_books[2] = (struct book){ "The Odyssey", "Homer"                    };

	print_books(my_books, 3);

	return 0;
}
 
void print_books(const struct book books[], int n)
{
	for (int i = 0; i < n; ++i)
		printf("%s Written by %s.\n", books[i].name, books[i].author);
}

 

동적할당을 활용한 구조체 포인터

#define LEN 30

struct book
{
	char name[LEN];
	char author[LEN];
};

void print_books(const struct book *books, int n);

int main() {
	struct book* my_books = (struct book*)malloc(sizeof(struct book) * 3);
	if (my_books == NULL) exit(1);

	my_books[0] = (struct book){ "The Great Gatsby", "F. Scott Fitzegrald" };
	my_books[1] = (struct book){ "Hamlet", "William Shakespear"            };
	my_books[2] = (struct book){ "The Odyssey", "Homer"                    };
	print_books(my_books, 3);

	return 0;
}
 
void print_books(const struct book *books, int n)
{
	for (int i = 0; i < n; ++i)
		printf("%s Written by %s.\n", (books+i)->name, (*(books+i)).author); // also books[i]
}

 

14.14 구조체 파일 입출력 연습문제

1. text 파일에 책 이름과 저자를 추가해보자. text파일 끝에 반드시 next line character를 추가해주어야 한다.

2. text 파일 입출력을 binary 형식(wb, rb)로 처리하면 빠르다.

3. binary 파일을 읽을 수 없기 때문에 디버깅이 어렵다.

4. malloc은 컴파일 타임에 크기 변수로 오버플로우 될지 확인할 수 없다.

   calloc을 사용해주자. 오버플로우가 될지 미리 계산한다.

#define LEN 50

struct book
{
	char name[LEN];
	char author[LEN];
};

void print_books(const struct book *books, int n);

void write_books(const char* filename, const struct book* books, int n);
void read_books_ref(const char* filename, struct book** books_dptr, int* n);
struct book* read_books_val(const char* filename, int* n);

void write_books_binary(const char* filename, const struct book* books, int n);
struct book* read_books_binary(const char* filename, int* n);

int main() {
	int temp;
	int n = 3;
	const char* file_n = "books.bat";
	struct book* my_books = (struct book*)malloc(sizeof(struct book) * n);
	if (my_books == NULL) {
		printf("Malloc failed");
		exit(1);
	}

	my_books[0] = (struct book){ "The Great Gatsby", "F. Scott Fitzegrald" };
	my_books[1] = (struct book){ "Hamlet", "William Shakespear"            };
	my_books[2] = (struct book){ "The Odyssey", "Homer"                    };
	
	print_books(my_books, n);
	
	printf("\n Writing to a file.\n");
	write_books(file_n, my_books, n);
	//write_books_binary(file_n, my_books, n);

	free(my_books);
	n = 0;
	printf("Done.\n");

	printf("\n Press any key to read data from a file.\n\n");
	temp = getchar(); // waiting

	my_books = read_books_val(file_n, &n);
	//read_books_ref(file_n, &my_books, &n);
	//my_books = read_books_binary(file_n, &n);

	if (my_books == NULL)
	{
		printf("my_books has null\n");
		exit(1);
	}

	print_books(my_books, n);
	free(my_books);
	n = 0;

	printf("Finished Successfully \n");

	return 0;
}
 
void print_books(const struct book *books, int n)
{
	for (int i = 0; i < n; ++i)
		printf("Book %i : %s Written by %s\n", i, (books+i)->name, (*(books+i)).author); // also books[i]
}

void write_books(const char* filename, const struct book* books, int n)
{
	FILE *fp = fopen(filename, "w");
	fprintf(fp,"%i\n", n);
	for (int i = 0; i < n; ++i)
		fprintf(fp, "%s\n%s\n", books[i].name, books[i].author);
	fclose(fp);
}

struct book* read_books_val(const char* filename, int* n)
{
	FILE* fp = fopen(filename, "r");

	int flag = 0;
	flag = fscanf(fp, "%i%*c", n); // Asterisk for deleteing

	//struct book* books = (struct book*)malloc(sizeof(struct book) * (*n));
	struct book* books = (struct book*)calloc(sizeof(struct book) ,*n );
	if (books == NULL) exit(1);

	/* Using fsacnf */
	for (int i = 0; i < *n; ++i)
	{
		flag = fscanf(fp, "%[^\n]%*c%[^\n]%*c", books[i].name, books[i].author);
		if (flag != 2) exit(2);
	}
	fclose(fp);
	return books;

	/* Using fread, Complicated way*/
	//unsigned char ch;
	//unsigned char buffer[LEN] = { '\0', };
	//int buffer_count = 0;
	//bool isOn = 1;
	//int count = 0;
	//
	//while( fread(&ch, sizeof(unsigned char), 1, fp) > 0 ){
	//	if (ch == '\n'){
	//		buffer_count = 0;
	//		if (isOn) { strcpy(books[count].name, buffer); }
	//		else { strcpy(books[count].author, buffer); }
	//		isOn = isOn ? 0 : 1;
	//		for (int i = 0; i < LEN; ++i)
	//			buffer[i] = '\0';
	//		if (isOn == 1) { count++; }
	//		continue;
	//	}
	//	buffer[buffer_count++] = ch;
	//}
	//fclose(fp);
	//return books;
}
 
void read_books_ref(const char* filename, struct book** books_dptr, int* n)
{
	FILE* fp = fopen(filename, "r");

	int flag; // count how many words are read
	flag = fscanf(fp, "%d%*c", n); // delete last elements of character == '\n'
	if (flag != 1) exit(1);

	// malloc can't calculate the size of memory allocating to 
	//*books_dptr = (struct book*)malloc(sizeof(struct book) * (*n) ); // Make Waring
	*books_dptr = (struct book*)calloc(sizeof(struct book), *n);
	if (*books_dptr == NULL) exit(1);

	/* You can try this on much understandable way
	    struct book *books = (struct book*)calloc(sizeof(struct book), *n);
	    ...
	    *books_dptr = books; */

	for (int i = 0; i < *n; ++i)
	{
		flag = fscanf(fp, "%[^\n]%*c%[^\n]%*c", (*books_dptr)[i].name, (*books_dptr)[i].author);
		if (flag != 2)
		{
			printf("Read more than two variable in the books_dptr\n");
			exit(1);
		}
	}
	fclose(fp);
}

void write_books_binary(const char* filename, const struct book* books, int n)
{
	FILE* fp = fopen(filename, "wb");
	if (fp == NULL)
	{
		printf("Can't open file\n");
		exit(1);
	}

	/* Good Solution */
	fwrite(&n, sizeof(n), 1, fp);
	fwrite(books, sizeof(struct book), n, fp);
	fclose(fp);

	/* Skip to save empty '\0' */
	//fwrite(&n, sizeof(1), 1, fp);
	//unsigned int len;
	//for (int i = 0; i < n; ++i)
	//{
	//	len = 0;
	//	while (len < LEN)
	//	{
	//		if (books[i].name[len] == '\0')
	//			break;
	//		len++;
	//	}
	//	fwrite(books[i].name, 1, len, fp);
	//	fwrite("\n", 1, 1, fp);

	//	len = 0;
	//	while (len < LEN)
	//	{
	//		if (books[i].author[len] == '\0')
	//			break;
	//		len++;
	//	}
	//	fwrite(books[i].author, 1, len, fp);
	//	fwrite("\n", 1, 1, fp);
	//}
	//fclose(fp);

	/* Don't squeeze array to '\0' chacter */
	//fwrite(&n, sizeof(1), 1, fp);
	//for (int i = 0; i < n; ++i)
	//{
	//	fwrite(books[i].name, 1, LEN, fp);
	//	fwrite(books[i].author, 1, LEN, fp);
	//}
	//fclose(fp);
}

struct book* read_books_binary(const char* filename, int* n)
{
	FILE* fp = fopen(filename, "rb");

	fread(n,sizeof(*n), 1, fp);

	printf("n=%i\n", *n);
	struct book* books = (struct book*)malloc(sizeof(struct book) * (*n));
	if (books == NULL)
	{
		printf("Faill to allocate memory in read_books_binary");
		exit(1);
	}
	fread(books, sizeof(struct book), *n, fp);
	fclose(fp);

	return books;
}

 

14.15 공용체의 원리

공용체 vs 구조체

공용체 특징 구조체
O 메모리 공유 X
X Padding O

공용체는 서로 다른 자료형이 같은 메모리 주소를 공유한다(같은 저장공간을 사용한다)

 

공용체를 만들어 주소를 확인해보자

  1. 공용체 멤버변수 중 저장공간이 가장 많이 필요한 double을 기준으로 메모리를 할당받았다.

  2. 공용체, 공용체 멤버변수는 모두 같은 주소값을 공유하고있다.

/* Union
   -Store different data types in the same memory space  */
union my_union
{
    int		i;
    double	d;
    char	c;
};
union my_union uni;

typedef union{
    int		i;
    double	d;
    char	c;
}simple_union;
simple_union sim_uni;

printf("size of union : %zd\n", sizeof(union my_union));
printf("addres  union : %p \n", &uni);
printf("each member's add :%p %p %p \n", &(uni.i), &(uni.d), &(uni.c));

union my_union union_list[10];
printf("size of union arr : %zd\n", sizeof(union_list));

Output

size of union : 8
addres  union : 007AFB94
each member's add :007AFB94 007AFB94 007AFB94
size of union arr : 80

 

공용체 멤버 변수에 값을 할당하고 다른 변수가 어떻게 변하는지 보자

uni.c = 'A';
printf("%c %x %d \n", uni.c, (int)uni.c, uni.i);
// 0xCCCCCC41 = -858993599

uni.i = 0;
uni.c = 'A';
printf("%c %x %d \n", uni.c, (int)uni.c, uni.i);
// 0x00000041 = 65

uni.d = 1.2;
printf("%c %x %d %f \n", uni.c, (int)uni.c, uni.i, uni.d);
// 33 33 33 33 33 33 f3 3f (Read memory right to left)
// double: 0x 3ff3 3333 3333 3333 = 1.19999999999999995...
// int   : 0x 3333 3333           = 858993459
// char  : 0x 33                  = 51 = '3'

Output

A 41 -858993599
A 41 65
3 33 858993459 1.200000 // first 3 is '3' = 51

 

Union's Assignment Opeartor, Pointer and Usage

	/* Initializing union */
	// You can assigning only one member variable.
	union my_union uni_assignment = uni;
	union my_union uni_first_only = { 10 }; // { 10, 3.14 } <= Error!
	union my_union uni_designate  = { .c = 'A' };
	union my_union uni_donot      = { .d = 1.23, .i = 100 }; // Do not use in this way
	printf("%c %f %d \n", uni_donot.i, uni_donot.d, uni_donot.c); // >> d 0.000000 100

	uni.i = 123;
	uni.d = 1.2;
	uni.c = 'k';
	printf("%c %f %d \n", uni.i, uni.d, uni.c);                   // >> k 1.200000 107

	union my_union* uni_ptr = &uni; // Pointer to Union
	int x = uni_ptr->c; 

	uni.c = 'A';                 // Don't do this uni.d has 'A's 
	double readl = 3.14 * uni.d; // binary data is converting to double

 

14.16 공용체와 구조체를 함께 사용하기

멤버 변수 여럿 중 단 하나만 사용할 때, 멤버 변수를 서로 배타적으로 사용할 때 공용체를 사용해보자.

주로 구조체와 함께 사용한다.

struct personal_owner
{
	char rrn_h[7]; // Resident Registration Number
	char rrn_t[8]; // 830422 - 1185600
};
struct company_owner
{
	char crn_h[4]; // Company Registration Number
	char crn_m[3]; // 111 - 22 - 33333
	char crn_t[6];
};

union data
{
	struct personal_owner pers; // mutually exclusive
	struct company_owner  comp;
};
struct car_data
{
	char model[15];
	int status; /* 0 = psersonal, 1 = compnay */
	union data ownerinfo;
};

void print_car(struct car_data car)
{
	printf("-------------------------------------------\n");
	printf("Car model : %s\n", car.model);
	if (car.status == 0) /* personal = 0, company = 1 */
		printf("Personal Owner : %s-%s\n", car.ownerinfo.pers.rrn_h,
			car.ownerinfo.pers.rrn_t);
	else
		printf("Company Owner : %s-%s-%s\n", car.ownerinfo.comp.crn_h,
			car.ownerinfo.comp.crn_m, car.ownerinfo.comp.crn_t);
	printf("-------------------------------------------\n");
}

int main()
{
	struct car_data my_car = { .model = "Avante", .status = 0, 
		.ownerinfo.pers = {.rrn_h = "990830",.rrn_t = "1123456"} };
	
	struct car_data company_car = { .model = "Sonata", .status = 1,
		.ownerinfo.comp = {.crn_h = "111", .crn_m = "22",.crn_t = "33333"} };

	print_car(my_car);
	print_car(company_car);
	return 0;
}

 

14.17 익명 공용체

위 코드에서 union data를 struct 안에서 익명으로 만들 수 있다. 멤버 변수를 한 번 덜 호출할 수 있어 편하다.

struct car_data
{
	char model[15];
	int status; /* 0 = psersonal, 1 = compnay */
	union{
		struct personal_owner pers; // mutually exclusive
		struct company_owner  comp;
	};
};

전체 코드는 다음과 같다.

struct personal_owner
{
	char rrn_h[7]; // Resident Registration Number
	char rrn_t[8]; // 830422 - 1185600
};
struct company_owner
{
	char crn_h[4]; // Company Registration Number
	char crn_m[3]; // 111 - 22 - 33333
	char crn_t[6];
};

struct car_data
{
	char model[15];
	int status; /* 0 = psersonal, 1 = compnay */
	union{
		struct personal_owner pers; // mutually exclusive
		struct company_owner  comp;
	};
};

void print_car(struct car_data car)
{
	printf("-------------------------------------------\n");
	printf("Car model : %s\n", car.model);
	if (car.status == 0) /* personal = 0, company = 1 */
		printf("Personal Owner : %s-%s\n", car.pers.rrn_h,
			car.pers.rrn_t);
	else
		printf("Company Owner : %s-%s-%s\n", car.comp.crn_h,
			car.comp.crn_m, car.comp.crn_t);
	printf("-------------------------------------------\n");
}

int main()
{
	struct car_data my_car = { .model = "Avante", .status = 0,
		.pers = {.rrn_h = "990830",.rrn_t = "1123456"} };

	struct car_data company_car = { .model = "Sonata", .status = 1,
		.comp = {.crn_h = "111", .crn_m = "22",.crn_t = "33333"} };

	print_car(my_car);
	print_car(company_car);
	return 0;
}

 

같은 자료형이지만 다양한 식별자를 사용하고 싶다면 공용체를 사용하자

  배열 식별자와, 개별 식별자를 함께 사용할 때 특히 유용하다

struct Vector2D {
    union {
        struct { double x, y; };
        struct { double i, j; };
        struct { double arr[2]; };
    };
};
typedef struct Vector2D Vec2;

Vec2 v2= { 3.14, 2.99 };
printf("%.2f %.2f\n", v2.x, v2.y);           // >> 3.14 2.99       
printf("%.2f %.2f\n", v2.i, v2.j);           // >> 3.14 2.99
printf("%.2f %.2f\n", v2.arr[0], v2.arr[1]); // >> 3.14 2.99

for(int i=0; i<2; ++i)
    printf("%.2f ",v2.arr[i]);

 

14.18 열거형(Enumerated Types)

숫자를 이용하여 다양한 변수나 상태를 표현하려면 다음과 같은 방법이 있다.

  1. 숫자 - 특징 대응 표 만들기

  2. #define으로 정의하기

	// Red:0, Orange:1, Yellow:2, Green:3, ...
	// this make programmer hard to remember everything
	int color = 0;
	if (color == 0) { printf("red\n"); }
	else if (color == 1) { printf("orange\n"); }
	else if (color == 2) { printf("yellow\n"); }

	// Preprocessor coping every define words to it's usage
	// Compiler can't catch Any Errors if define words has problem
#define RED 1
#define ORANGE 2
#define YELLOW 3
	color = YELLOW;
	if (color == YELLOW) { printf("Yellow\n"); }

각 방법의 문제가 있다. 열거형을 쓰면 간단하다.

 

열거형 사용해보기

- 열거형을 사용하면 정수 상수를 기호, 단어로 부를 수 있다.

- 열거형은 읽기 쉽고 유지보수에 유리하다.

- 컴파일러가 확인 할 수 있어 에러 발견이 쉽다.

	/*
		Enumberated type
		- Symbolic names to represent integer constants
		- Improve readability and make it easy to maintain
		- 'enum' specifier ( 'struct' specifier, 'union' specifier)

		Enumerators
		- The symbolic constatns
	*/

	enum spectrum { red, orange, yellow, green, blue, violet }; // each variable is Enumerator
	//               0     1       2       3     4       5

	enum spectrum color; // you can assign any Enumerator of this type
	color = blue;        // like this
	if (color == yellow)
		printf("yellow\n");
	
	for (color = red; color <= violet; color++) // Note: ++operator doesn't allowed in C++
		printf("%d \n", color);                  // in that case use type (int) 
	printf("red = %d, orange = %d\n", red, orange);

 

열거형은 개별 변수의 값을 정해줄 수 있다

 이를 활용하여 if 조건문에 특정 정수 상수를 넣어줄 수 있다. 유지보수가 쉽다.

	enum levels { low = 100, medium = 500, high = 2000 };

	int score = 700; // TODO : user input
	if (score > high)
		printf("High score!\n");
	else if (score > medium)
		printf("Good job!\n");
	else if (score > low)
		printf("Not bad\n");
	else
		printf("Do your best\n");

 

할당해주지 않은 변수는 자동으로 할당된다. 다음 예제를 보자.

	enum pet { cat, dog = 10, lion, tiger };
	printf("cat  = %d\n", cat);     // >> cat = 0
	printf("lion = %d\n", lion);    // >> lion = 11
	printf("tiger = %d\n", tiger);  // >> tiger = 12

 

14.19 열거형 연습문제

열거형을 이용하여 for반복문을 쉽게 사용할 수 있다.

for(color = red; color<=blue; ++color)

 

문제풀이

#define LEN 30

enum spectrum { red, orange, yellow, green, blue };
const char* colors[] = { "red", "orange", "yellow", "green", "blue" };

int main()
{
    char choice[LEN] = { 0, };
    enum spectrum color;
    bool color_is_found = false;

    while (1)
    {
        printf("Input any color you want(Enter to quit)\n");
        if (scanf("%[^\n]%*c", choice) != 1)
        {
            printf("Good day to see you\n");
            exit(0);
        }

        //for (color = red; color <=blue; ++color) { ... }
        for (int i = 0; i < 5; ++i)
        {
            if (strcmp(choice, colors[i]) == 0)
            {
                color_is_found = true;
                color = i;
                break;
            }
        }
        if (color_is_found)
            printf("%s is %i\n", colors[color], color);
        else
            printf("try it again\n");
        color_is_found = false;
    }
    return 0;
}

 

14.20 이름공간 공유하기(Namespace)

 Namespace
- Restricted space that program could recognize identifier
- Specifier's tag and his identifier could use same name (C++ block this)
- C++ : You can declare duplicated identifier with different namespace in one place

1. 같은 Scope 내에 중복된 식별자를 가질 수 없다. 자료형이 달라도 마찬가지다.

double myname = 3.14; 
int myname = 345;   //Error!
enum myname { Kim, Lee, Park };
enum myname myname= Kim;
int myname = 345; // Error

다른 Scope라면 중복된 식별자를 가질 수 있다. 이 때 바깥 Scope에 있는 식별자는 내부에서 사용할 수 없다

int myname = 123;
{
    int myname = 345; // myname Out of Scope is hiding
}

 

2. specifier에서 사용한 tag는 해당 변수의 식별자로 사용할 수 있다. (C++에서 금지한다) 사용하지 말자

struct test {
    int i;
    int j;
};
struct test test = { .i = 1, .j = 2 };
int test = 111; // Error!
struct rect { double x; double y; };
struct rect rect = { .x = 1.1, .y = 2.2 }; 

/* Struct two and int two are in different categories */
struct two { int one; int two; };
int two = 123; // OK in C (Error in C++) but not recommend

 

3. typedef, specifier, identifier 3개 이상 겹치면 안된다

아래 예제에서 thr을 three로 바꾸거나 fo를 four로 바꾸었을 때 컴파일 에러가 발생한다.

	struct three { int one; int two; int three; };
	typedef struct three three;
	three thr = { 1, 2 };
	printf("thr %i %i \n", thr.one, thr.two);    // OK

	struct four { int one; int two; };
	typedef struct four fo;
	fo four = { 1, 2 };
	printf("four %i %i \n", four.one, four.two); // OK

 

같은 Namespace에서 함수명과  변수명이 동일하면 Error

int iamfunction()
{
	return 0;
}

int main()
{
	int i = iamfunction(); // Error!
	int iamfunction = iamfunction(); // Error!
}

 

Namespace Pollution

  전역변수는 다른 파일에서 선언되더라도 중복된 식별자를 가질 수 없다. Namespace 내부에 정의하면 해결가능하다.

// other.c
int i = 5;


// main.c
int i = 5;
int main()
{
    // Error Occured at Linking
	return 0;
}

 

14.21 함수 포인터의 원리

함수 실행의 의미

  컴파일러는 식별자를 메모리 주소로 번역하여 찾아간다. 해당 주소에 저장되어 있는 명령어를 실행한다.

 

프로그램은 어떻게 실행될까?

  코드작성 -> 컴파일 -> 실행파일 생성(하드디스크에 저장) -> OS에 요청(실행파일 실행)

   -> OS: 프로그램 메모리를 복사, RAM에 붙여넣기 -> RAM의 TextSegment(Read Only)에 저장된다

위 코드에서 message 자기 자신의 주소는 Stack에 저장되어 있다. message 값은 TextSegment에 저장되어있다.

f_ptr(함수 포인터) 자기 자신의 주소는 Stack에 저장되어 있다. f_ptr(함수 포인터) 값은 TextSegment에 저장되어있다.

main(함수)의 이름은 포인터다. main 함수는 TextSegment에 저장되어있다.

 

예제

  1. 함수포인터는 해당 함수 주소값을 저장한다.

  2. 함수의 이름은 포인터이므로 Ampersand(&)를 사용하지 않아도 된다. (사용해도 된다)

  3. 포인터 문법대로 Indirection하여 사용할 수 있다. 물론 Indirection 없이도 사용가능하다

void func1()
{
	printf("func1 is working\n");
}
int func2(int i)
{
	printf("func2 is working\n");
	return i * -1;
}
double func3(int i, double d)
{
	printf("func3 is working\n");
	return i + d;
}

int main()
{
// return  identifier  Parameter   Should be match to declaration
    void     (*pf1)      ()      = func1; // = &func1 also work
    int (*pf2)(int) = func2;

    double (*pf3)(int, double) = func3;

    pf1();
    (*pf1)();

    int test2 = pf2(5);
    int test2_ = (*pf2)(5);
    printf("%i %i", test2, test2_);
}

 

14.22 함수 포인터의 사용 방법

다음 코드에서 ToUpper함수와 ToLower함수는 Parameter와 반환값의 자료형이 동일하다.

/* ToUpper and ToLower has same form so you can use same func ptr */
void ToUpper(char* str) // Convert Every char to Uppercase
{
	while (*str)
	{
		*str = toupper(*str);
		str++;
	}
}

void ToLower(char* str)
{
	while (*str)
	{
		*str = tolower(*str);
		str++;
	}
}

int main()
{
	char str[] = "Hello, World!";
	void (*func_ptr)(char*);

	func_ptr = ToUpper;
	// func_ptr = &ToUpper;      // Acceptable
	// func_ptr = ToUpper(str); // Not Acceptable in C, 
	                           // It execute function and assign it's value on func_ptr

	printf("String Literal   add %p\n", ("Hello, World!"));
	printf("Function pointer add %p\n", ToUpper);
	printf("Variable         add %p\n", str);

	func_ptr(str);
	(*func_ptr)(str); // Accep in ANSI, Not in K&R
	printf("ToUpper >> %s \n", str);

	func_ptr = ToLower;
	func_ptr(str);
	printf("ToLower >> %s \n", str);

	return 0;
}

 

함수 포인터를 활용하여 ToUpper함수와 ToLower함수를 간단하게 사용하자

void UpdateString(char* str, int(*func_ptr)(int))
{
    while (*str)
    {
        *str = func_ptr(*str); // (*func_ptr)(*str)
        str++;
    }
}
int main()
{
    char str[] = "Hello, World!";

    UpdateString(str, toupper);
    printf("Update toupper >> %s \n", str);

    UpdateString(str, tolower);
    printf("Update tolower >> %s \n", str);
}

 

14.23 자료형에게 별명을 붙여주는 typedef

특징

- 특정 자료형을 다른 이름으로 부를 수 있다.

- 새로운 자료형을 만들지 않는다.

 

장점

 1. 사용의도를 자료형에 나타낼 수 있다.

 2. 짧게 쓸 수 있다.

 3. 사용 환경에 맞게 자동으로 변환시킬 수 있다.

 

typedef의 장점 4.Portable(이식성이 높다)

개발 환경마다 다른 자료형을 가지는 자료형을 쉽게 사용하기 위해 typedef를 사용할 수 있다. 아래 코드에서 size_t는 x86, x64 환경에서 각각 다른 값을 가진다.

typedef unsigned char BYTE;
BYTE x, y[10] = { 0, }, * z = &x;
{
    typedef unsigned char byte;

    size_t s = sizeof(byte); // unsigned int (x86), unsigned long long (x64)

    /* above code solve this imcompatible code problem */
    // unsigned int s = sizeof(byte);       // x86
    // unsigned long long s = sizeof(byte); // x64
}
// byte b; // Error! typedef belong to above scope

size_t의 정의를 찾아가자. 다음은 typedef의 활용법을 잘 보여주고 있다.

#ifdef _WIN64
    typedef unsigned __int64 size_t
#else
    typedef unsigned int     size_t;

 

 자주 사용하는 함수time의 반환값은 환경에 따라 달라진다. time_t가 typede로 정의되어 있기 때문에 가변적으로 사용가능하다.

// #include <time.h>
/*  This fuction returns the time since 00:00:00 UTC, 
    January 1, 1970 (Unix time stamp) in seconds      */
time_t t = time(NULL);
printf("%lld\n", t);

  time_t의 정의를 찾아가면 typedef를 활용하고 있다.

// corecrt.h
#ifndef _CRT_NO_TIME_T
    #ifdef _USE_32BIT_TIME_T
        typedef __time32_t time_t;
    #else
        typedef __time64_t time_t;

// ...
typedef long                          __time32_t;
typedef __int64                       __time64_t;

 

typedef vs #define

  - 전처리기는 #define에 등록된 Macro를 해당 값으로 복사/붙여넣기 한다.

  - 에러가 만들기 쉬우니 typedef를 권장한다.

  /*  - typedef interpretation is performed by the compiler, not the preprocessor
      - more flexible than #define                                              */
  typedef char* STRING;
  STRING name = "John Wick", actor = "Keanu Reeves";
  printf("typedef %s %s\n", name, actor);

#define STRING_C char*
  STRING_C name_c = "Daenerys Targaryen", *actor_c = "Emilia Clarke";
  printf("#define %s %s\n", name_c, actor_c);

STRING를 char*로 대체했다. *actor_c에서  Asterisk(*)가 없으면 char 자료형이 된다. 실수하기 쉽다.

 

 

복습) typedef를 활용하여 구조체 자료형 나타내기

typedef struct complex {
    float real;
    float imag;
}COMPLEX; // it makes skip to type "typedef struct comple COMPLEX"

typedef struct { double width; double height; }rect; // No tag
rect rec1 = { 1.1, 2.2 };
rect rec2 = rec1;

 

복잡한 함수포인터 typedef로 해석하기

  복잡한 포인터를 어떻게 해석하는지 다음 장에서 다룬다.

  typedef를 사용하면 간단하게 표현할 수 있다는 점만 기억하고 넘어가자.

/*  "One Good way to understand complicated 
     declarations is small steps with typedef "
     - K&R book Chapter 5.12                    */

char char3[3] = { 'A', 'B', '\0' };

/* Complicated Function Declarations */
char (*complicated_func())[3] // Returns Pointer to char[3]
{
	return &char3; // Returns a pointer pointing to char[3]
}

char(*(*fptr1)())[3] = complicated_func; // So messy

/* bring me in peace and calm */
typedef char(*FuncReturningPtr())[3];  // Function Return Pointer to Char[3]
typedef char(*(*PTRFuncReturningPtr)())[3];

FuncReturningPtr* fptr2 = complicated_func;
PTRFuncReturningPtr fptr3 = complicated_func;

typedef char c_triple[3];
c_triple* complicated_func_simp()
{
	return &char3;
}

int main()
{
	char(*returned)[3] = fptr1();
	c_triple *returned2 = fptr2();
	c_triple *returned3 = fptr3();
	c_triple *returned4 = complicated_func_simp();
	printf("%c %c %c %c \n", (*returned)[0], (*returned2)[1], (*returned3)[0], (*returned4)[1] );

	return 0;
}

 

14.24 복잡한 선언을 해석하는 요령

실무에서 복잡한 선언을 보기 어렵다. 보통은 typedef로 간소화하여 사용하고 이해한다. 원리만 알고가자.

 * indicates a pointer
() indicates a function, not priority
[] indicates an array	

Deciphering Complex Declarations (KNK 18.4)
- Always read declarations from the inside out.
- When there's a choice, always favar [] and () over *(asterisk)

  1. Identifier 식별자를 찾는다.

  2. 식별자를 기준으로 항상 안쪽에서 바깥쪽으로 읽어라

  3. [ ] 와 ( ) 는  *(asterisk) 보다 우선순위를 높게 해석한다.

 

복잡한 함수 포인터 살펴보기

int temp(int a)
{
	return 0;
}
int (*g(int a))(int) // Not used in pratically
{                    // Normally convert to typdef style
	return temp;
}

  위 사례처럼 복잡한 g함수포인터는 사용하지 않는다. typdef를 이용하여 간소화 해줌이 바람직하다.

 

반환값이 복잡할 때 typedef로 간소화 하기

int* ap[10]; // Identifier ap is array saving pointer

typedef int* ptr_int;
ptr_int typedef_ap[10];

float* fp(float); // fp is function takes float parameter, return float ptr

typedef float* ptr_float;
ptr_float fp(float);

 

복잡한 함수 포인터를 해석해보자

void(*pf)(int);
/*  void(*pf)(int);
          1         :: 1. this is pointer
               2    :: 2. take int type parameter So function ptr
      3             :: 3. return void type                        */

int* (*x[10])(void);
/* int* (*x[10])(void);
             1         :: 1. array save 10 elem
          2            :: 2. pointer
                  3    :: 3. take void type parameter, so Function ptr
    4                  :: 4. return int pointer                      */

 

잘못된 사례

  1. 함수는 배열을 반환할 수 없다.

    - 배열을 가리키는 포인터는 반환할 수 있다.

/* A fuction can't return an array */
int f(int)[]; // Error! - Wrong

/* But it can return a pointer to an array */
int(*f(int))[];

 

 

  2. 함수는 함수를 반환할 수 없다.(C한정) 

    - 함수가 함수를 가리키는 포인터를 반환할 수 있다.

    /* A function can't return a function */
    int g(int)(int); // Error! - Wrong

    /* But it can return a pointer to function*/
    int (*g(int))(int);

 

  3. 여러 함수를 담은 배열은 존재할 수 없다.

    - 함수포인터를 담은 배열은 가능하다.

        /* An array of functions are impossible */
	int a[10](int); // Error! - Wrong

	/* But an array of function pointers are possible */
	int* (*x2[10])(void);

x2는 함수포인터를 담은 배열이다. 이를 간단하게 나타내보자.

/* Using typedef to simplify declaration */
	typedef int FCN(int);
	typedef FCN* FCN_PTR;
	typedef FCN_PTR FCN_PTR_ARRAY[10];
	FCN_PTR_ARRAY x3;

 

추가 예제

	int board[6][4]; // an array of arrays of int
	int** db_ptr;

	int* risk[10];   // Array saves 10 in pointer
	int(*risk)[10];  // pointer pointing to Array saving 10 int

	int* off[3][4];  // 4*3 Array saves 12 int pointers
	int(*uff)[3][4]; // Pointer pointing to 4*3 int Array
	int(*uof[3])[4]; // Array saves 3 pointers pointing to 4 element int-array

int(*uof[3])[4]가 해석하기 어렵다. 3개의 int-pointer가 4개짜리 배열을 각각 가리키고 있다.

 

	char* fump(int);       // function returning char*
	char (*frump)(int);    // function pointer, pointed function return char
	char (*flump[3])(int); // array of 3 pointers to functions that return type char

char (*flump[3])(int)가 해석하기 어렵다. parameter와 반환값은 쉽게 예측이 가능하다. (*flump[3])는 배열 안에 함수 포인터가 3개 있음을 가리킨다.

 

	typedef int arr[5];
	typedef arr* p_arr;
	typedef p_arr ten_p_arr[10];

	arr five_arr;
	p_arr ptr_five_arr;
	ten_p_arr ptr_five_arr_ten_times;

 

14.25 qsort 함수 포인터 연습문제

예제를 보고 float 자료형으로 구현해보자

#include <stdio.h>
#include <stdlib.h>
int compare(const void* first, const void* second)
{
	if (*(int*)first > *(int*)second)
		return 1;
	else if (*(int*)first < *(int*)second)
		return -1;
	else
		return 0;
}
int main()
{
	int arr[] = { 8, 2, 5, 3, 6, 11 };
	int n = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, n, sizeof(int), compare);

	for (int i = 0; i < n; ++i)
		printf("%d ", arr[i]);
}

 

struct 자료형으로 구현하기

struct kid
{
	char name[100];
	int height;
};

// TODO: Try increasing / decreasing order
int compare_kid(const void* first, const void* second)
{

	     if (((struct kid*)first)->height > ((struct kid*)second)->height)
		return 1;
	else if (((struct kid*)first)->height < ((struct kid*)second)->height)
		return -1;
	else
		return 0;

	//typedef struct kid* kid_ptr;
	//kid_ptr kid_ptr_1 = (kid_ptr)first;
	//kid_ptr kid_ptr_2 = (kid_ptr)second;
	//if (kid_ptr_1->height > kid_ptr_2->height)
	//	return 1;
	//else if (kid_ptr_1->height < kid_ptr_2->height)
	//	return -1;
	//else
	//	return 0;
}


int main()
{
	struct kid friends[] = { "Jack Jack", 40, "Geenie", 300, "Aladdin", 170, "Piona", 150 };
	//struct kid friends[] = { {"Jack Jack", 40}, {"Geenie", 300}, {"Aladdin", 170}, {"Piona", 150} };

	const int n = sizeof(friends) / sizeof(struct kid);
	qsort(friends, n, sizeof(struct kid), compare_kid);
	for (int i = 0; i < n; ++i)
		printf("%s      \tis %3i height\n", friends[i].name, friends[i].height);
}

구조체 배열을 정의할 때 각 구조체 변수의 값을 직렬로 늘려놓아도 된다

 

 

14.26 함수 포인터의 배열 연습문제

void update_string(char* str, int(*pf)(int));
void ToUpper(char* str);
void ToLower(char* str);
void Transpose(char* str);

int main()
{
	char option[] = { 'u', 'l' }; // Let's add 't'
	int n = sizeof(option) / sizeof(option[0]);
	
	typedef void (*FUNC_TYPE)(char*);
	FUNC_TYPE operations[] = { ToUpper, ToLower }; // you can add Transpose
	
	printf("Enter a string\n>> ");
	char input[100];

	int scanned;
	while ((scanned = scanf("%[^\n]%*c", input)) != 1)
		printf("Please try again\n");;

	while (1)
	{
		printf("Choose an option:\n");
		printf("u ) to upper\n");
		printf("l ) to lower\n");

		char c;
		while (scanf("%c%*[^\n]*c", &c) != 1)
			printf("Please try again\n");

		bool found = false;

		//switch (c)
		//{
		//case 'u':
		//	ToUpper(input);
		//	found = true;
		//	break;
		//case 'l':
		//	ToLower(input);
		//	found = true;
		//	break;
		//}
		
		/* Easy to implement */
		for (int i = 0; i < n; ++i)
		{
			if (option[i] == c)
			{
				(*(operations[i]))(input);
				found = true;
				break;
			}
		}

		if (found) break;
		else printf("Wrong Input, try again\n");
	}
	printf("result = %s \n", input);
}

void update_string(char* str, int(*pf)(int))
{
	while (*str)
	{
		*str = (*pf)(*str);
		str++;
	}
}

void ToUpper(char* str)
{
	while (*str != '\0')
	{
		*str = toupper(*str);
		str++;
	}
}
void ToLower(char* str)
{
	while (*str != '\0')
	{
		*str = tolower(*str);
		str++;
	}
}

void Transpose(char* str)
{
	while (*str)
	{
		if (islower(*str))
			*str = toupper(*str);
		else if (isupper(*str))
			*str = tolower(*str);
		str++;
	}
}
Comments