배움 저장소

[홍정모의 따라하며 배우는 C언어] 15.비트다루기 본문

Programming Language/C

[홍정모의 따라하며 배우는 C언어] 15.비트다루기

시옷지읏 2021. 12. 7. 20:01

15.1 비트단위 논리 연산자

비트단위 논리 연산자는 낱개로 사용한다

C언어에서 거듭제곱 연산자(^)를 지원하지 않는다. ^연산자는 bitwise에서만 사용한다.

 

Bit 연산자의 활용

  bitwise 연산을 통해 메모리 낭비를 줄일 수 있다

 

 

Bitwise And &

 

Bitwise OR |

 

Bitwise Excluesive OR ^

 

 

Bitwise Not~

 

15.2 이진수를 십진수로 바꾸기 연습문제

#include <math.h> // pow()

unsigned char to_decimal(const char binary_digit[]);

int main()
{
	printf("Binary (string) to Decimal conversion\n");

	printf("%d\n", to_decimal("00000110"));
	printf("%d\n", to_decimal("00010110"));

	// Note : ^ (caret) means power in math. (int) pow(2,3) == 8

	printf("%d\n", to_decimal("10010100"));

	printf("reaching end!\n");
	return 0;
}

unsigned char to_decimal(const char binary_digit[])
{
	const size_t bits = strlen(binary_digit);
	double sum = 0;
	for (size_t s = bits ; 0!=s; s--)
	{
		if (binary_digit[s-1] == '1')
			sum += pow(2, bits - s);
		else if (binary_digit[s-1] != '0')
			exit(1);
	}
	return (unsigned char)sum;
}

 

주의

for (size_t s = bits ; 0<=s; s--)

 해당 for 반복문은 infinite loop에 빠진다. size_t자료형은 unsigned int 형태로 0~큰 수의 범위를 가진다. 만약 0보다 작아지면 큰 수가 되어버려 무한한 반복문이 실행된다. for 반복문은 변화식이 적용된 후 조건식을 검사하기 때문에 size_t=0에서 unary -- 연산자가 실행되면 UINT_MAX가 되어버린다.

 이를 해결할 수 있는 방법은 세 가지가 있다. 단 첫 번째 사례처럼 Overflow를 만드는 방법은 권하지 않는다.

// check wheter s is UINT_MAX
for (size_t s = bits-1 ; s!=UINT_MAX; s--) // O
{
	if (binary_digit[s] == '1')
		sum += (int)pow(2, bits - s - 1);
	else if (binary_digit[s] != '0')
		exit(1);
}

// use index-1
for (size_t s = bits ; s!=0; s--)
{
	if (binary_digit[s-1] == '1')
		sum += (int)pow(2, bits - s);
	else if (binary_digit[s-1] != '0')
		exit(1);
}

// reverse order with variable
size_t size = 8;
for (size_t i = 0; i < size; ++i)
{
    if ((unsigned char)(saved_num / pow(2, size-1-i)) == 1) 
    {
        putchar('1');
        saved_num -= (unsigned char)pow(2, size-1-i);
    }
    else { putchar('0'); 
}

  1. s를 UINT_MAX와 비교하여 overflow 되었는지 검사하기

  2. index -1을 사용하기

  3. index를 0부터 증가시켜 사용하기

 

 

15.3 &를 이용해서 십진수를 이진수로 바꾸기 연습문제

- & 연산자를 이용하면 쉽게 십진수를 이진수로 바꿀 수 있다.

- size_t 자료형은 Linux, windows, x86, x64 모두 작동한다. 자주 사용해주자.

- 아래 예제에서 print_binary함수는 음수를 bit로 나타낼 수 없다. bitwised operator를 애용하자!

- to_decimal 함수내 sum 변수의 자료형이 double이다. pow 함수의 반환형이 double이기 때문에 형변환이 필요하다

unsigned char to_decimal(const char bi[]);
void print_binary(const unsigned char num);
void print_bitwised(const unsigned char num);

int main()
{
	unsigned char i    = to_decimal("01000110");
	unsigned char mask = to_decimal("00000101");

	print_binary(i);
	print_binary(mask);
	print_binary(i & mask);

	print_bitwised(i);
	print_bitwised(mask);
	print_bitwised(i & mask);
}

unsigned char to_decimal(const char bi[])
{
	double sum= 0;
	size_t size = strlen(bi);
	for (size_t t = 0; t < size; ++t)
	{
		if (bi[t] == '1') { sum += (double)pow(2, size - 1 - t); }
		else if (bi[t] != '0') { exit(1); }
	}
	return (unsigned char)sum;
}

void print_binary(const unsigned char num)
{
	printf("input is %3hu ", num);
	unsigned char saved_num = num;
	for (size_t i = 8; 0 < i; --i)
	{
		if ((unsigned char)(saved_num / pow(2, i-1)) == 1) {
			putchar('1');
			saved_num -= (unsigned char)pow(2, i-1);
		}
		else { putchar('0'); }
	}
	printf("\n");
}

void print_bitwised(const unsigned char num)
{
	printf("input is %3hu ", num);
	for (size_t i = 8; 0<i; --i)
	{
		unsigned char mask = (unsigned char)pow(2, i-1);
		if ( (num & mask) != 0) { putchar('1'); }
		else { putchar('0'); }
	}
	printf("\n");
}

 

15.4 비트단위 논리 연산자 확인해보기

Regular Logical Operators : && || !

bool have_apple = true;
bool like_apple = treu;
if(have_apple && like_apple)
eat_apple();

Bitwise Logical Operators:
- Bitwise NOT ~
- Bitwise AND &
- Bitwise OR  |
- Bitwise Exclusive OR ^

 

 이전에 구현한 함수로 논리연산자의 결과를 출력해보자

	unsigned char a = 6;
	unsigned char b = 5;

	printf("a     = %3hhu ", a);
	print_bitwised(a);

	printf("b     = %3hhu ", b);
	print_bitwised(b);

	printf("a & b = %3hhu ", a&b);
	print_bitwised(a&b);

	printf("a | b = %3hhu ", a | b);
	print_bitwised(a | b);

	printf("a ^ b = %3hhu ", a ^ b);
	print_bitwised(a ^ b);

	printf("~a    = %3hhu ", ~a);
	print_bitwised(~a);

Output

a     =   6 00000110
b     =   5 00000101
a & b =   4 00000100
a | b =   7 00000111
a ^ b =   3 00000011
~a    = 249 11111001

위 print_bitwised( )함수 내에서 첫 printf( ) 코드를 지워주면 위와 같은 결과값이 나온다.

 

15.5 2의 보수 표현법 확인해보기

부호크기 표현(Sign-magnitude representation)과 1의 보수법은 양수 0과 음수 0을 구분한다

Signed Integers
- Sign-magnitude representation
00000001 is 1  and 10000001 is -1
00000000 is +0 and 10000000 is -0
Two zeros +0 -0
from -127 to +127

- One's Complement Method
To reverse the sign, invert each bit.
00000001 is 1  and 10000000 is -1
11111111 is -0
from -127 to 127

 

2의 보수법은 음수 0과 양수0 이 없고 하나만 사용한다

- Two's Complement method (commonly used in most systems)
To reverse the sign, invert each bit and add 1.
from -128 to +127

 

음수 정수를 Bit로 나타내기 위하여 Bitwise Operator를 사용하자

  Bitwise &를 사용하여 구현해야 음수값을 제대로 출력할 수 있다.

void print_binary(const char num)
{
	printf("norm Decimal %4i \t", num);
	char saved_num = num;
	for (size_t i = 8; 0 < i; --i)
	{
		if ((char)(saved_num / pow(2, i - 1)) == 1) {
			putchar('1');
			saved_num -= (char)pow(2, i - 1);
		}
		else { putchar('0'); }
	}
	printf("\n");
}

void print_bitwised(const char num)
{
	printf("Bitw Decimal %4i \t", num);
	for (size_t i = 8; 0 < i; --i)
	{
		char mask = (char)pow(2, i - 1);
		if ((num & mask) != 0) { putchar('1'); }
		else { putchar('0'); }
	}
	printf("\n");
}

int main()
{
	print_bitwised(127);
	print_bitwised(-127);
	print_bitwised(~127+1);

	print_binary(127);
	print_binary(-127);
	print_binary(~127+1);
}

Output

Bitw Decimal  127       01111111
Bitw Decimal -127       10000001
Bitw Decimal -127       10000001
norm Decimal  127       01111111
norm Decimal -127       00000000
norm Decimal -127       00000000

 

2의 보수법을 적용하는 과정을 출력해보자

print_bitwised(12);
print_bitwised(~12 + 1);
print_bitwised(-12);

print_bitwised(7);
print_bitwised(~7 + 1);
print_bitwised(-7);

Output

Bitw Decimal   12       00001100
Bitw Decimal  -12       11110100
Bitw Decimal  -12       11110100
Bitw Decimal    7       00000111
Bitw Decimal   -7       11111001
Bitw Decimal   -7       11111001

 

15.6 비트단위 쉬프트 연산자 (비트 이동 연산자)

 비트단위 연산자를 2^n의 곱셈과 나눗셈에 활용할 수 있다.

  음수일 때 Right shift를 2^n의 나눗셈으로 사용할 수 없다.

Bitwise shift operators
- Left shift
number << n : multiply  number by 2*n 

- Right shift
number >> n : divide by 2^n (for non-negative numbers)

 

Bitwise shift operator 왼쪽에 있는 변수를 binary로 변환해서 생각하자

  쉬프트 연산자는 해당 Binary 숫자의 자리를 이동시킨다. 이때 저장공간 밖으로 나간 정보는 사라진다.

//                           // Left Number(optr)
// 8 bit integer cases       //  76543210          76543210         76543210
printf("%4hhd \n", 1 << 3);  //  00000001   ->  00000001???     ->  00001000
printf("%4hhd \n", 8 >> 1);  //  00001000   ->     ?00001000    ->  00000100

 

부호를 가지는 정수는 자리 이동을 하면 어떻게 될까?

ㄴright shift

  음수 정수를 다룰 때 Right shift를 2^n의 나눗셈으로 사용할 수 없다.

  >>를 하면 기존값이 양수/음수에 따라 다른 값을 채운다.  sign을 나타내는 bit가 1이면 1을 채우고 0이면 0을 채운다.

  아래 예제에서 -119는 signed 이고 137은 unsigned로 두 값의 binary 모양은 동일하다.

//                             // Left Number(optr)
printf("%4hhd \n", -119 >> 3); //  10001001   ->     ???10001001  ->  11110001 ( -15)
printf("%4hhd \n",  137 >> 3); //  10001001   ->     ???10001001  ->  00010001 (  17)

이 때 -119 >> 3 결과값이  -119 / 8과 다름을 주의하자.

printf("%4hhd \n", -119 >> 3); // -15 
printf("%4hhd \n", -119 / 8);  // -14

따라서 음수일 때 비트단위 right('>>') 쉬프트연산자는 2의 거듭제곱을 나눗셈 한 것과 다르다.

 

양수일 때는 동일하다

printf("%4hhd \n",  137 >> 3); // 17
printf("%4hhd \n",  137 / 8 ); // 17

 

Left Shift

  <<를 하면 양수/음수 동일하게 0을 채운다.

printf("%4hhd \n",  137 << 4); //  10001001   ->     10001001???? ->  10001000 (-122)
printf("%4hhd \n", -119 << 4); //  10001001   ->     10001001???? ->  10001000 ( 144)

 

Pow 함수를 Bitwise operator로 구현하기

  pow함수보다 Bitwise operator로 구현했을 때 더 효율적이다

void int_binary(const int num)
{
	printf("Decimal %12d == Binary ", num);
	const size_t bits = sizeof(num) * CHAR_BIT;
	for (size_t i = 0; i < bits; ++i)
	{
		const int mask = 1 << (bits - 1 - i); // instead of pow()
		if ((num & mask) == mask)             // is much efficent way
			putchar('1');
		else
			putchar('0');
	}
	printf("\n");
}

 

다음은 0xffffffff를 signed과 unsigned로 표현했을 때의 값이다

printf("Unsigned int %10u \n", 0xffffffff); // Unsigned int 4294967295
printf("signed   int %10d \n", 0xffffffff); // signed   int - 1
int_binary(0xffffffff);     // Decimal  - 1 == Binary 11111111111111111111111111111111

 

right shift >> 3

printf("Right shift by 3\n");
int_binary(  (signed)0xffffffff >> 3); // Decimal       - 1 == Binary 11111111111111111111111111111111
int_binary((unsigned)0xffffffff >> 3); // Decimal 536870911 == Binary 00011111111111111111111111111111

>>3 연산에서 새로 들어온 값의 초기값을 확인해보자. signed일 때와 unsigned의 음수일 때 초기값이 다르다.

 

기호 bit가 동일 할 때 right shift>>3

printf("Unsigned int %10u \n", 0x00ffffff); // Unsigned int   16777215
printf("signed   int %10d \n", 0x00ffffff); // signed   int   16777215
int_binary(  (signed)0x00ffffff >>3);//Decimal 2097151==Binary 00000000000111111111111111111111
int_binary((unsigned)0x00ffffff >>3);//Decimal 2097151==Binary 00000000000111111111111111111111

  결과값이 동일함을 알 수 있다. right shift 연산은 음수일 때만 주의하여 사용하자.

 

 

15.7 비트단위 연산자의 다양한 사용법

아이템 착용 여부를 예로들자. true/false 혹은 0/1이면 표현할 수 있고 1bit면 충분하다. 그러나 bool 자료형은 1byte이며 이는 컴퓨터가 자료를 주고받는 단위이기 때문에 7bit를 낭비할 수밖에 없다.

bool has_sword 	= false;
bool has_shield = false;
bool has_potion = false;
bool has_guntlet = false;

bool has_hammer = false;
bool has_key = false;
bool has_ring = false;
bool has_amulet = false;

위 8개의 변수는 총 7bit * 8을 낭비하고 있다. bit mask를 사용하여 낭비를 줄이자!

 

bit mask 활용하기

- 1bit의 정보를 shift 연산자를 사용하여 표시할 수 있다. 16진수도 사용할 수 있다.

- c에서 ^연산자를 사용할 수 없어 pow를 사용한다.

- c++는 binary 숫자를 직접 넣어 표현할 수 있다.

//                     Shift     Decimal    Binary      Hex   Octet
#define MASK_SWORD    ( 1 << 0) // 2^0     0000 0001    0x01   01
#define MASK_SHILED   ( 1 << 1) // 2^1     0000 0010    0x02   02
#define MASK_POTION   ( 1 << 2) // 2^2     0000 0100    0x04   04
#define MASK_GUNTLET  ( 1 << 3) // 2^3     0000 1000    0x08   010
#define MASK_HAMMER   ( 1 << 4) // 2^4     0001 0000    0x10   020
#define MASK_KE       ( 1 << 5) // 2^5     0010 0000    0x20   040
#define MASK_RING     ( 1 << 6) // 2^6     0100 0000    0x40   0100
#define MASK_AMULET   ( 1 << 7) // 2^7     1000 0000    0x80   0200

 

위 데이터를 활용하려면 masking이 필요하다

  and(&)연산자를 이용하면 mask에 표시되어 있는 자료만 가져온다.

       flag    0101 1010
&      mask    0000 0011
-------------------------
mask & falg == 0000 0010

 

Bitmask 활용하기

  특정 bit를 ON(아이템 착용)

printf("\nGet Swrod and Amulet\n");
flags = flags | MASK_SWORD; // flag |= MASK_SWORD // is On!
char_binary(flags);

flags |= MASK_AMULET;
char_binary(flags);

 

  특정 bit를 OFF(아이템 버리기)

printf("\nGet postion and use it\n");
flags |= MASK_POTION; // flag |= MASK_SWORD // is On!
char_binary(flags);

flags = flags & ~MASK_POTION;
char_binary(flags);

 

  특정 bit를 ON/OFF Toggling(장비세트 토글링)

printf("\nToggling Items\n");
flags = flags ^ MASK_HAMMER;
char_binary(flags);
flags = flags ^ MASK_HAMMER;
char_binary(flags);
flags = flags ^ MASK_HAMMER;
char_binary(flags);

 

  특정 Bit 확인(아이템 소유여부 확인)

printf("\n Checking the Value of a Bit \n");
if ((flags & MASK_KEY) == MASK_KEY)
    printf(" >> You can enter. \n");
else
    printf(" >> You can not enter. \n");

flags |= MASK_KEY; // Obtained a Key!

if ((flags & MASK_KEY) == MASK_KEY)
    printf(" >> You can enter. \n");
else
    printf(" >> You can not enter. \n");

 

  Bitmask로 Trimming(필요한만큼 잘라쓰기)

	int int_flag = 0xffffffff; 
	// 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
	int_binary(int_flag);

	int_flag &= 0xff;
	// Trim by 1111 1111 ( Cutting )
	int_binary(int_flag);

Output

Decimal  -1 == Binary  :11111111111111111111111111111111
Decimal 255 == Binary  :00000000000000000000000011111111

 

15.8 RGBA 색상 비트 마스크 연습문제

#define BYTE_MASK 0xff
unsigned int rgba_color = 0x66CDAAFF;
// 4bytes, medium aqua marine (102, 205, 170, 255)

unsigned char red, green, blue, alpha;

// Use right shift >> operator
alpha = rgba_color & BYTE_MASK;
blue  = rgba_color>>8 & BYTE_MASK;
green = rgba_color>>16 & BYTE_MASK;
red   = rgba_color>>24 & BYTE_MASK;
printf("R, G, B, A = %hhu, %hhu, %hhu, %hhu \n", red, green, blue, alpha);

 unsigned int는 4byte 자료형이다.4byte는 32bit이다. 2^32를 16진수로 표현하려면 (2^4)^8이므로 8자리가 필요하다.

이때 16진수의 한 자리수(0~16)은 4bit를 표현하고 있다. 16진수의 두 자리수는 8bit이다.

 rgba_color는 8bit 단위로 색을 구분하고 있다. 따라서 8bit를 이동해야 16진수에서 그 다음 자리수의 값을 당겨온다.

 

15.9 구조체 안의 비트 필드

비트 필드 : 여러 비트를 나열하여 정보를 저장하고 있는 장소

Bitwised 연산자를 사용하지 않아도 된다. 편리하다.

 

15.10 비트필드의 사용방법

비트필드 선언

struct items
{
	bool has_sword : 1;  // number means bits to use.
	bool has_shiled : 1; 
	bool has_potion : 1;
	bool has_guntlet : 1;
	bool has_hammer : 1;
	bool has_key : 1;
	bool has_ring : 1;
	bool has_amulet : 1;
}items_flag;

 

 각 멤버 변수가 컴퓨터 저장되는 순서는 Compiler에 따라 다르다.

   MSVC는 먼저 선언된 멤버 변수가 낮은 자리 비트에 저장된다. 출력 함수는 편의에 따라 높은 비트를 먼저

  출력해주어야 하므로, 저장된 순서를 뒤집어주면 된다.

   위 비트필드의 크기는 1byte이므로 unsigned char 자료형을 Parameter로 사용하였다.

void char_to_binary(unsigned char uc)
{
	const int bits = CHAR_BIT * sizeof(unsigned char);
	for (int i = bits - 1; i >= 0; i--)
		printf("%d", (uc >> i) & 1);
}
void print_binary(char* data, int bytes) // Note: extended for any sizes
{
	for (int i = 0; i < bytes; ++i)
		char_to_binary(data[bytes - 1 - i]);
	printf("\n");
}

int main()
{
	printf("item's object takes %zd bytes \n", sizeof(items_flag)); // >> 1byte

	items_flag.has_sword   = true; // c 99 accept to assign bool type instead of number
	items_flag.has_shiled  = 1;
	items_flag.has_potion  = 0;
	items_flag.has_guntlet = 1;
	items_flag.has_hammer  = 0;
	items_flag.has_key     = 0;
	items_flag.has_ring    = 0;
	items_flag.has_amulet  = 0;
	
	// The order of member varaible was 
	print_binary((char*)&items_flag, sizeof(items_flag));
}

Output

item's object takes 1 bytes
00001011

 

15.10 비트필드의 사용방법

공용체를 사용하면 전체 메모리 관리를 쉽게 할 수 있다

 메모리 공간을 공유하는 변수 unsigned char 자료형 값에 0을 할당하면 간단하게 비트필드를 초기화할 수 있다.

 아래 1<<4는  0000 0001 을 4만큼 왼쪽으로 밀어서

                    0001 0000 5번째 자리에 1이 있음을 유의하자.

union
{
    struct items bf;
    unsigned char uc;
}uni_flag;

uni_flag.uc = 0;

uni_flag.bf.has_amulet = 1; // equip amulet
print_binary((char*)&uni_flag, sizeof(uni_flag));

uni_flag.uc |= (1 << 4);    // equip 5's member variable
print_binary((char*)&uni_flag, sizeof(uni_flag));

uni_flag.bf.has_hammer = 0; // abandaon hammer(5's member)
print_binary((char*)&uni_flag, sizeof(uni_flag));

Output

item's object takes 1 bytes
10000000
10010000
10000000

 

비트 필드 이용 사례 보기 - Windows DOS

  아래 코드를 보자. unsigned int 자료형을 사용하되 5bit만 할당하고있다.

struct file_time
{
    unsigned int seconds : 5; // 2^5 = 32, 0 ~ 30*2 seconds
    unsigned int minutes : 6; // 2^6 = 64, 0 ~ 60 minutes
    unsigned int hours   : 5; // 2^5 = 32, 0 ~ 23 hours
};

struct file_date
{
    unsigned int dat	 : 5; // 2^5 = 32,     0 ~ 31
    unsigned int month	 : 4; // 2^4 = 16,     1 ~ 12
    unsigned int year    : 7; // 2^7 = 128,    1980 ~ 
}fd;

2^5는 32가지 경우의 수를 나타낼 수 있다. 0~31의 수를 표현가능하다.

 

사용예제

/* 1988 12 28 */
fd.day = 28;
fd.month = 12;
fd.year = 8;
printf("Day %u, Month %u, Year %u \n", fd.day, fd.month, fd.year);

Output

 Day 28, Month 12, Year 8

 

 이 때 fd.day가 32bit를 사용하므로 (0~31)의 숫자를 사용할 수 있다. 범위 밖의 숫자를 사용하면 Overflow가 발생한다.

fd.day = 33;
fd.month = 18;
fd.year = 8;
printf("Day %u, Month %u, Year %u \n", fd.day, fd.month, fd.year);

Output

Day 1, Month 2, Year 8

 

비트 필드와 Scanf( )

  이 때 Scanf( )로 해당 변수에 값을 입력하려면 다음과 같은 에러가 발생한다. scanf( )는 최소단위 1byte를 기준으로

  값을 입력받기 때문에 bit를 쪼개어 사용한 비트필드 멤버변수에 값을 할당할 수 없다.

scanf("%d", &fd.day); // Error!

 

 

15.11 비트필드의 패딩

다음 코드를 실행하여 결과값을 확인해보자

	struct 
	{
		bool option1 : 7;
		bool option2 : 1;
	}bbf;
	printf("%zu bytes \n", sizeof(bbf));

	struct {
		unsigned short option1 : 8;
		unsigned short option2 : 7;
		unsigned short option3 : 1;
	}usbf;
	printf("%zu bytes \n", sizeof(usbf));

	struct {
		unsigned int option1 : 1;
		unsigned int option2 : 1;
	}uibf;
	printf("%zu bytes \n", sizeof(uibf));

Output

1 bytes
2 bytes
4 bytes

  첫 번째 비트 필드는 8bit 

  두 번째 비트 필드는 16bit

  세 번째 비트 필드는 2bit   를 할당하였으나 4bytes 공간을 차지하고 있다.

비트 필드 구조체의 최소 크기는 멤버 변수의 자료형 크기이다.

 

구조체 안에 다른 자료형을 넣으면 어떻게 될까?

struct 
{
    bool option1 : 1;
    bool option2 : 1;
    unsigned long long option3 : 1;
}bbf;
printf("%zu bytes \n", sizeof(bbf));

  출력값은 16bytes가 된다. unsigned long long은 8bytes를 차지한다. 이 때 정보를 주고받는 단위가 8byte이다. 

  unsigned long long을 끊기지 않고 한 번에 전송하기 위하여 앞에 있는 두 bool 자료형 변수를 메모리에 할당한

 이후 패딩을 넣어주었다. 따라서 8bytes 8bytes의 저장공간을 가진다.

 

확인해보기

memset( (char*)&bbf, 0xff, sizeof(bbf) );

이 함수는 해당 변수를 0xff으로 초기화해주고 있다. 이 때 0xff는 1111 1111이다. 

위 함수를 사용하여 메모리 공간에서 패딩을 어디에서 추가해주었는지 확인해보자.

// memset reset memory's value to argument 
memset((char*)&bbf, 0xff, sizeof(bbf));
print_binary((char*)&bbf, sizeof(bbf));
bbf.option1 = 0;
bbf.option2 = 0;
bbf.option3 = 0;
print_binary((char*)&bbf, sizeof(bbf));
printf("%zu bytes\n", sizeof(bbf));

출력은 값 변경 전과 후이다.

끝에 있는 두 0은 option1과 option2에 할당해준 0값이다. 중간에 있는 0은 unsigned long long에 할당해준 값 0이다.

option3에 (unsigned long long 자료형) 1을 할당하면 저 값이 1이 되지만 2를 할당하면 0이 되어버린다.

 

 

패딩을 확인하기 위하여 unsigned long long 변수에서 16bit를 사용해보자

struct
{
    bool option1 : 1;
    bool option2 : 1;
    unsigned long long option3 : 16;
}bbf;
	memset((char*)&bbf, 0x00, sizeof(bbf));

다시 memset 함수를 사용하여 모든 값을 0으로 초기화해주고

memset((char*)&bbf, 0x00, sizeof(bbf));
print_binary((char*)&bbf, sizeof(bbf));
bbf.option1 = 1;
bbf.option2 = 1;
bbf.option3 = 0xffff;
print_binary((char*)&bbf, sizeof(bbf));
printf("%zu bytes\n", sizeof(bbf));

출력해보자. 아랫 줄의 출력에 패딩이 들어가 있음을 확인할 수 있다.

 

메모리 강제할당하기

  자료형 : 0 문법을 사용하면 해당 자료형의 크기만큼 메모리를 더 할당받을 수 있다. 하드웨어 가속 때 쓰인다.

struct 
{
    bool option1 : 1;
    bool         : 0; // force to allocate memory
    bool option2 : 1;
}bbf;
printf("%zu bytes\n", sizeof(bbf));

struct {
    unsigned short option1 : 1;
    unsigned short option2 : 1;
    unsigned short         : 0; // force to allocate memory
    unsigned short option3 : 1;
}usbf;
printf("%zu bytes \n", sizeof(usbf));

struct {
    unsigned int option1 : 1;
    unsigned int         : 0; // force to allocate memory        
    unsigned int option2 : 1;
}uibf;
printf("%zu bytes \n", sizeof(uibf));

 

15.12 메모리 줄맞춤(정렬) alignof, alignas

변수나 배열의 주소 간격을 알려준다. 지정할 수도 있다. MSVC에서 지원하지 않는다.

#include <stdio.h>
#include <stdalign.h> // c++ style alignas, alignof without it, add underscore '_'

int main()
{
#ifdef __clang_major__
    printf("Clang detected version %d.%d\n", __clang_major__, __clang_minor__);
#endif
    
#ifdef __GNUC__
    printf("Gcc detected version %d.%d\n", __GNUC__, __GNUC_MINOR__);
#endif

	printf("Alignment of char = %zu\n", alignof(char));
	printf("alignof(float[10])= %zu\n", alignof(float[10]));
	printf("alignof(struct{char c; int n;}) = %zu\n", alignof(struct { char c; int n }));
	
}

Output

Gcc detected version 8.4
Alignment of char = 1
alignof(float[10])= 4
alignof(struct{char c; int n;}) = 4
Hello, World!

첫 번쨰 줄 alignof(char)은 1byte이다. 줄맞춤 최소 단위가 1byte임을 의미한다.

두 번째 줄 alignof(배열)은 4byte이다. 배열을 alignof에 넣으면 각 원소의 줄맞춤 최소 단위를 출력한다.

세 번째 줄 alignof(구조체)는 4byte이다. 이 때 최대 크기의 구조체 멤버변수를 기준으로 줄맞춤 최소단위를 맞춘다.

              char은 1byte int는 4byte이므로 4byte으로 줄맞춤한다.

 

 

줄맞춤 된 주소 확인해보기

  줄맞춤이 되었다면 해당 주소의 주소지가 자료형의 크기로 나누어진다.

double dx;
char ca;
char cx;
double dz;
char cb;

char cz;

//printf("char alignmnet  : %zd\n", _Alignof(char));
//printf("double alignmnet: %zd\n", _Alignof(double));
printf("char alignmnet: %zd\n", alignof(char));
printf("char alignmnet: %zd\n", alignof(double));

printf("&dx: %p %lld\n", &dx, (long long)&dx % 8);
printf("&ca: %p \n", &ca);
printf("&cx: %p \n", &cx);
printf("&dz: %p %lld\n", &dz, (long long)&dz % 8);
printf("&cb: %p \n", &cb);
printf("&cz: %p \n", &cz);

Output

char alignmnet: 1
char alignmnet: 8
&dx: 0x7ffc1b55c178 0
&ca: 0x7ffc1b55c177 
&cx: 0x7ffc1b55c176 
&dz: 0x7ffc1b55c168 0
&cb: 0x7ffc1b55c167 
&cz: 0x7ffc1b55c166

 

이 때 align을 사용하여 줄바꿈 기준을 바꾸어줄 수 있다. alignas를 사용할 때 변수는 2의 제곱값만 가능하다. 아래 코드를 보면 char 자료형임에도 불구하고 8, 16 기준을 적용시킬 수 있음을 알 수 있다. 

char cz;
char _Alignas(double) cz1;
char alignas(16) cz2;

printf("&cz : %p %lld\n", &cz,  (long long)&cz  % 8);
printf("&cz1: %p %lld\n", &cz1, (long long)&cz1 % 8);
printf("&cz2: %p %lld\n", &cz2, (long long)&cz2 % 8);

Output

&cz : 0x7ffc569ccddf 7
&cz1: 0x7ffc569ccdd8 0
&cz2: 0x7ffc569ccdd0 0

 

align 함수를 배열에 적용하기

unsigned char alignas(long double) c_arr[sizeof(long double)];
Comments