경일게임아카데미 멀티 디바이스 메타버스 플랫폼 개발자 양성과정 20220503 2022/04/04~2022/12/13

Jinho Lee·2022년 5월 3일
0

경일 메타버스 20220503 5주차 2일 수업내용. C언어 - 복습, 문자열 리터럴, 열거형, 구조체와 패딩, 비트필드, 공용체, 함수와 가변 인자

포인터 복습

  1. Type : 데이터를 해석하는 방법

  2. Memory : 데이터를 저장하는 공간

  3. Object : 데이터를 저장하는 영역 -> Memory

    int a ;
    
    a = 10;
  • 연산자는 피연산자를 동반한다.

    • a + b 등 : 할당 연산자는 피연산자가 2개 필요하다.

    • 피연산자의 조건 => 타입.

    • 모든 연산자는 피연산자의 조건(타입)이 있다.

      int* p;
      
      p = &a;
      
      10 + 20;
      
      a[b] = *(a + b);
      
  1. 왜 a + b는 주소 연산일까? => 피연산자의 타입 때문에 주소 연산이란 걸 알게 됨

    => a의 타입은 pointer이고, b의 타입은 int

  2. *연산자는 오직 pointer 타입에만 적용할 수 있음 => (a + b)의 타입은 pointer

  3. 역참조한 값의 타입은? pointer가 가리키고 있는 타입.

    (p + 2); //
    
    (arr + 2); // arr(value) + sizeof(type) * int
    
    // &arr[0] + sizeof(int) * 2;
    
    //
int arr2[2][3]; // 2차원 배열의 주소 계산 순서

(*(arr2 + 2) + 3);

arr2 + 2; // arr2 : int(*)[3];

// 주소 연산

// &arr2[0] + sizeof(int[3]) * 2;

// 첫 번째 원소로부터 24바이트 뒤에 있는 것을 참조.

// arr2 + 2 : int(*)[3];

- (arr2 + 2); // int[3]
- (int* + 3); // *(int*)

int;

arr[2][3];

문자열 리터럴

  • 문자열 리터럴의 저장 위치 => 데이터 영역 => 수정 불가능한 영역
char* str1 = "Hello";

str1[2] = 'o';  // printf("%s", str1); => Hello

char str4[] = "Hello";

str4[3] = 'o';  // printf("%s", str4); => Heloo

const char* str2 = "World";

const char* str3 = "Metaverse";
  • C++에서 추가되는 내용들
    • C++에서의 캐스팅(명시적 변환)
      • static_cast
      • const_cast
      • reinterpret_cast
      • dynamic_cast
      • OOP 등등
    • C++은 공부할 게 진짜 많다!

열거형

// 열거형은 아래와 같이 사용한다.
// enum [identifier] { enumerator-list }
// 식별자는 생략 가능하다.
 
enum DIR
{
    // 열거형에 나열한 기호 상수는 모두 int 타입의 값을 가진다.
    DIR_LEFT,   // 첫 번째 값을 지정해주지 않을 시 0이다.
    DIR_RIGHT,  // 두 번째부터는 값을 지정하지 않을 시
                // 이전 값의 + 1 이다.
    DIR_UP = 3, // 명시적으로 값을 지정할 수도 있다.
    DIR_DOWN,
    DIR_UP_LEFT,
    DIR_UP_RIGHT,
    DIR_DOWN_LEFT,
    DIR_DOWN_RIGHT
};

// 식별자만 사용하면 C언어가 알아듣지 못한다.
// 꼭 앞에 enum을 명시해야 한다.
enum DIR playerDirection = DIR_LEFT; // 값은 열거형에 있는 기호 상수를
                                     // 이용해도 되고
playerDirection = 3; // 정수를 직접 지정할 수도 있다.
                     // 열거형과 int 타입 간에는 변환이 일어난다.
playerDirection = -1; // 열거형에 아예 없는 값을 지정할 수도 있다.
  • 타입이기 때문에 위와 같이 객체를 만드는 것도 가능하다.
  • 열거형의 크기는 int 타입과 같다. // sizeof enum DIR; // 4

구조체

// 구조체의 정의는 아래와 같다.
// struct [identifier] { struct-declaration-list }
struct Student
{
    int Age; // 구조체를 구성하는 타입을 멤버(Member)라고 한다.
    enum { A, B, O, AB } BloodType;
    char Name[24];
    struct Student* BestFriend; // 자신의 타입도 멤버로 사용할 수 있다.
};
// 열거형과 마찬가지로 앞에 struct를 붙여야
// Student라는 식별자를 인식할 수 있다.
struct Student seonmun = { 20, A, "최선문", NULL };
// 멤버가 정의된 순서대로 값을 넣어주게 된다.
// Age = 20, BloodType = B, Name = “최선문”, BestFriend = NULL
  • 메모리에는 순서에 맞춰 일직선으로 표현된다.

  • 객체를 만들고 초기화 하는 방법은 위와 같다. 레퍼런스는 아래대로이다.
    https://en.cppreference.com/w/c/language/struct_initialization

  • 멤버에 접근하는 것은 연산자를 사용하며, 포인터를 이용해 접근할 때는 -> 연산자를 사용한다.

// 당연한 얘기지만 주소 연산을 활용한다.
seonmun.Age; // &seonmun에 접근하면 값을 얻을 수 있다. 값은 20
seonmun.BloodType; // &seonmun + sizeof(int)에 접근하면
                   // 값을 얻을 수 있다. 값은 B(1)
seonmun.Name; // &seonmun + sizeof(int) + sizeof(enum)에 접근하면
              // 값을 얻을 수 있다. 값은 "최선문"
seonmun.BestFriend; // &seonmun + sizeof(int) + sizeof(enum) +
                    // sizeof(Name)에 접근하면 값을 얻을 수 있다.
                    // 값은 NULL(0)
struct Student* p = &seonmun;
 
// 각 멤버에 접근하려면 아래와 같이 한다.
(*p).Age;
 
// 하지만 매번 위처럼 쓰는 것이 귀찮으므로
// 이를 축약한 형태가 -> 연산자이다.
p->Age;

패딩

  • 구조체 크기는 일반적으로 멤버의 크기를 전부 더하면 된다. 다만, 패딩 바이트가 추가되는 경우가 있다.

  • 패딩 바이트 : 멤버 접근 빈도를 줄이기 위해 추가된다. 메모리 주소에 접근하려면 주소가 정렬되어 있어야 한다. 그렇지 않으면 데이터가 나눠져 저장될 수 있으므로 접근을 2번 해야 한다. 그래서 C 프로그램에서는 접근을 한 번에 하기 위해 패딩 바이트를 넣는다. ⇒ 메모리 낭비

  • 패딩은 멤버의 정렬 요건 중 가장 큰 값으로 맞추게 된다.

    • 패딩을 줄이려면 크기가 작은 것부터 큰 순서대로, 혹은 큰 것부터 작은 순서대로 정의해야 한다.
    • 이로써 가장 최소한의 패딩 바이트를 얻는다.
#include <stdio.h>

int main()
{
    struct S { char ch; double d; float f; } s; // 패딩 발생
		sizeof(struct
		{
		    char ch; // 1바이트 + 7바이트(패딩)
		    double d; // 8바이트
		    float f; // 4바이트 + 4바이트(패딩)
		}); // 24바이트
    alignof(s); // 24바이트
    
    sizeof(struct { char ch; float d; float f; });  // 패딩 해결
		sizeof(struct { char ch; float f; double d; });  // 패딩 해결
}

비트필드

  • 구조체를 비트 단위로 사용하는 방식. 비트 연산에 유용히 쓸 수 있다.

공용체

  • 바이트를 나눠 사용하는 구조체. 멤버 중 가장 큰 크기의 데이터를 공용으로 쓴다.
    다음 예시에서는 int의 4바이트를 나눠 쓰게 된다.
union Color
    {
        struct
        {
            unsigned char R;    //  1바이트
            unsigned char G;    //  1바이트
            unsigned char B;    //  1바이트
            unsigned char A;    //  1바이트
        };
        unsigned int Value; //  4바이트
    };

    union Color color;
    color.R = 0;
    color.G = 47;
    color.B = 167;
    color.B = 255;
    // 클라인 블루
    color.Value = 0xFFAB4700;
    // 멤버 중 가장 큰 크기의 데이터를 가지고 나눠쓰는 구조체

함수

가변 인자(Variadic Argument)

#include <stdio.h>
#include <stdarg.h>

// myprintf(3, 1, 2, 3) => My Variadic Function : 1, 2, 3
// "My Variadic Function : ~"
void myprintf(int count, ...)
{
    va_list args;                        // <stdarg.h>	// 가변 인자를 다룰 객체를 만든다.
    va_start(args, count);               // <stdarg.h>	// 가변 인자의 시작 위치를 알려준다.
    
    printf("My Variadic Function :");
    for (int i = 0; i < count; ++i)
    {
        int number = va_arg(args, int); // <stdarg.h>		// 인자를 빼서 사용한다.
        printf(" %d,", number);
    }

    va_end(args);                       // <stdarg.h>		// 끝낸다.
}

int main(void)
{
    myprintf(5, 1, 2, 3, 4, 5);
		
		return 0;
}

실습 : myprintf

  • 오답노트 안건 : 문자열 리터럴("Hello")은 데이터 영역 (변경 불가)에 저장되며, ""은 그 주소값을 보낸다
#include <stdio.h>
#include <stdarg.h>

// <summary>
// printf와 유사하게 동작하나 기능이 제한됨.
// %d / %c / %f / %s
// </summary>
// <param name="format">형식 문자열</param>
// <param name="">가변 인자값</param>
void myprintf(const char* format, ...);

int main(void)
{
	return 0;
}

void myprintf(const char* format, ...)
{
	va_list args;	// 가변 인자를 다룰 객체를 만든다.
	va_start(args, format);	// 가변 인자의 시작 위치를 알려준다.

	while (*format != '\0')
	{
		if (*format == '%')
		{
			//putchar(' ');

			if (*(format + 1) == 'd')
			{
				int i = va_arg(args, int);	// 인자를 빼서 사용한다.
											// 데이터 타입의 크기만큼 주소값을 받아 사용
				if (i == 0)
				{
					putchar(i + '0');
				}
				else
				{
					int digit[256] = { 0 };
					// int* Digit = digit; // 동적 할당 malloc

					int count = 0;
					if (i < 0)
					{
						putchar('-');
						i *= -1;
					}
					while (i != 0)
					{
						digit[count] = i % 10;
						// *(Digit + count) = i % 10;
						// Digit++;
						count++;
						
						i /= 10;
					}

					for (int k = count - 1; k >= 0; k--)
					{
						putchar(digit[k] + '0');
					}
					//for (int k = count - 1; k >= 0; k--)
					//{
					//	putchar(*(Digit + k) + '0');
					//}

					/*
					int tendigit = i / 10;
					int onedigit = i % 10;
					putchar(tendigit + '0');
					putchar(onedigit + '0');
					*/
				}
			}
			else if (*(format + 1) == 'c')
			{
				char c = va_arg(args, char);
				putchar(c);
			}
			else if (*(format + 1) == 's')	// 오답노트 안건 : 문자열 리터럴("Hello")은 데이터 영역 (변경 불가)에 저장되며, ""은 그 주소값을 보낸다
			{
				char* s = va_arg(args, char*);
				//s = "Hello";
				for (int k = 0; *(s + k) != '\0'; k++)
				{
					putchar(*(s + k));
				}
			}
			/*if (*(format + 2) != '\0')
			{
				putchar(',');
			}*/
			++format;
		}
		else
		{
			putchar(*format);
		}
		++format;
	}

	va_end(args);	// 끝낸다.
}

0개의 댓글