2021. 11. 1. 22:51ㆍc,c++ 기본/next step
안녕하세요 대학생개발자입니다.
저번글에서는 함수에 대한 내용을 다뤄봤습니다.
C언어의 내용을 순서대로 나열해보자면 이쯤에서 포인터를 다루는게 일반적이겠으나...
포인터변수에 대한 내용은 지금 다룰 내용은 아니고 그보다 더 원초적이고 기본이 될 만한 지식을 다지는 것이 더 중요합니다.
그럼 시작하도록 하겠습니다.
우선 전처리기에 대한 내용이 아주아주아주 살짝 나오게 될것입니다.
그리고 typedef와 const와도 좀 비교가 될 것이구요.
우선 우리가 자주 쓰는 전처리기 명령어 #include 와같은 이런 #이 붙은 명령어들이 전처리기 명령어인데요.
저도 사실 전처리기에 대해서는 잘 모르기 때문에 간단하게 정리하려고 합니다.
우선 말 그대로 전 처리기죠 컴파일 이전에 정해진 명령어들을 수행하는 녀석입니다.
헤더파일에 헤더가드로도 쓰이고 #define으로 상수 치환으로도 써버리고 혹은 pragma ... 을이용해서 어떠한 사전에 필요한 기능을 처리하기도 하죠
그럼 여기서 #include 다음으로 많이 사용하는 #define에 대해서 좀 알아보고 가겠습니다.
#define의 경우에는 말그대로 내가 define하는거죠 AA라는 keyword를 만나면 전부다 10으로 치환시켜라 가 되는거죠
아주 간단한 예제를 하나 보겠습니다
안타깝게 인텔리센스가 느려서... 하얀글씨로 떳습니다.
째든 여기서 Foo함수에 전달하는 AA는 10일겁니다 왜냐구요?
제가 10으로 정했으니까요 대신 이런 케이스에서는 문제점이 있습니다.
이건 컴파일러가 하는 행동이 아닙니다.
그저 컴파일 이전에 전처리기가 AA라는 문자열을 만났기때문에 10으로 치환해버리는 거죠 하지만 변수는 아닙니다.
왜냐구요?
컴파일러가 개입할때는 이미 AA는 10으로 바뀐 상태니까요...
사실 저경우에는 문제가 없습니다.
대신에... AA가 0으로 디파인 되어있고 a라는 변수는 포인터 변수라면요??
사실 0을 포인터에 대입할 수 있습니다.
근데 그게 의도된 것인지 아닌지 전혀 알.수.가 없다는 거죠...
그리고 내가 쓰지 않아야 될 곳에서도 쓰이게 될 수도 있습니다.
왜냐면 전역에 흩뿌려져 있는 상수 치환문이니까요...
물론 #define문은 스코프 안에 적어도 전역화 됩니다...
그리고 여기서 에러가 난다고해도 내가 알 수가없습니다...
왜냐면 치환문이니까요
그럼 첫번째로 전역에 널브러져 있는 케이스를 잡기위해서 열거형 타입을 하나 정의하는 방법을 생각해볼 수 있습니다.
이녀석은 스코프에 넣게되면 그 스코프 내에서만 사용할 수 있는 열거타입이 되니까요.
선언은 enum으로 할수있습니다.
하지만 열거형 타입또한... 내가 원하는 상황이 아닌 상황에도 쓸수가 있어요...
이렇기 때문에 우리는 enum class라는 걸 사용할겁니다.
enum class를 쓰게되면 그 클래스의 변수만 받을 수 있기 때문이죵...
그리고 다른 변수에 사용이 불가능하답니다.
제가 예전에 봤었던 effective c++에 이런 내용이 나와요 #define 대신에 const, enum, inline을 쓰라고...
물론 inline같은 경우는 c++이니까 지금 정리에서는 빠지겠지만 에러를 잡아주는 녀석은 컴파일러니까..
컴파일러한테 차라리 상수화를 맡기는 것이 더 좋다 이거에요.
전처리기는 에러를 잡아주는 친구가 아니니까요...
아... 그리고 #define으로 새로운 타입 정의를 할 수 도있죠...
물론 이것도 정의는 아니고 그냥 치환인거긴 하지만...
하지만 c에서는 typedef라는 키워드로 타입에 대한 다른이름으로 정의를 도와주고 있으니 다들 그걸 쓰자구요...
사실 typedef같은 키워드를 가장 많이 쓰는곳은 제가 생각할때는 API 인거같은데요...
API라고 한다면 당연히 언어에 종속적이면 안되는거라고 생각하거든요...
어떤 언어든간에 동일한 기능을 제공해야되고 어떤 기기든 간에 동일하게 처리를 해줘야되는데...
가끔 기기마다 int가 2바이트... 4바이트... 막... 달라지는 경우가 있어요.
물론 PC라면 4바이트로 동일할 확률이 99%이상이겠지만요.
케이스마다 다를수도 있지만 이건 os에 따라도 달라질 수도 있겠네요.
째뜬... 이런경우에는 모든 경우에 맞춰서 우리가 API를 개발할때 4바이트라는 개념을 확실히 해둘 필요가있어요.
어떤 컴파일러에서는 int가 2바이트가 int 타입으로 기능들을 정의했다가 어떤 컴파일러에서는 short라고 적었다가 하면 당연히 안되겠죠.
저는 그래서 이런경우에 typedef를 많이 쓰는거 같다고 생각해요.
그래서 이런 방식으로 진화를 하는거죠
#define -> enum -> enum class
#define -> typedef -> using
#define -> const -> constexpr
#define -> inline
같이요.
네... 그리고 const랑 비교를 했어야 됬는데 깜박하고 안해서 수정으로 다시 적고있습니다 ㅋㅋㅋㅋㅋ
const는 컴파일러에 의한 상수화입니다.
그래서 조사식이나 메모리뷰에서 볼 수가있습니다.
하지만 #define의 경우에는 그냥 치환이기때문에...
그냥 10이라는 숫자가 덩그러니있는겁니다...
조사식에서도 볼수없고 메모리뷰에서도 볼수없습니다...
이상입니다...
그래서 const를 씁시다 다들
아... 이건 저번에 하려다가 깜빡해서 여기다가 적어야겠어요
다들 switch의 점프테이블이 나오지 않고 너무 지저분 하게 나오는 그런 분기문들이 막 겹쳐서 있다면...
배열을 통해서 해보는 것도 괜찮은것 같아요 뭐 action[direction][actionType][position] 뭐 등등 이런식으로 했을때 하나의 스프라이트가 나오게 한다 이런것 괜찮아 보이지않나요?
물론 공간은 좀 먹겠지만...
뭐 어차피 점프테이블 만드렁도 공간 먹는건 동일하잖아요??
네 이런 테이블(다차원배열) 은 if분기문을 쪼개버리는데 아주 매우 매우 효과적이에요
그래서 쓰자구요...
아 그리고 Byte Padding rule에 대해서만 얘기하고 정리를좀 하려고해요
다들 바이트 패딩이 뭔지는 아실것 같은데...
이거에 대해서 정확하게 알고계신 분들이 그렇게 많이 없더라구요...
어떤분은 그냥... 가장 큰 변수의 크기에 맞추는것 아니냐?(일부맞는말)
아니면 그냥... 쭉 이어붙이는거 아니냐 등등..
이런식으로 다양하게 알고계신 분들이 많은데 확실하게 정리하겠습니다.
바이트 패딩은 메모리의 주소가 자신의 사이즈의 배수인 곳에 서는겁니다.
int a; char b; int c;를 하게되면
a는 4로 나눠지는 메모리번지수에 b는 1로 나눠지는 메모리번지수에 c도 4로나눠지는 메모리번지수에 세워야합니다.
그럼 이렇게되겠죠
a가 4로 나눠지는 곳에서고 b는 그다음 아무데나 붙어도 괜찮죠??
그리고 c는 4로나눠지는 곳에 서야되는데...
직전에 있던 변수가 1바이트였기때문에 그뒤에 이어서 서게되면 그 주소의 값을 4로 나머지 연산 했을 때 0이 나오지 않을겁니다...
이렇게되면 나중에 캐시에 대해서정리를 하면서 언급을 할건데요.
그 얘기를 할때 매우 곤란해지는 케이스가 생기기 때문에 그냥 3을 패딩을 집어넣고 c도 4의 경계에 세워버립니다.
바이트 패딩 생각보다 간단하죠?
그럼 여러분들이 아무런 구조체 하나 만들어놓고 실험해보세요...
사이즈 몇나오는지 왜 그렇게 나오는지...
생각보다 쉬운내용이니까요.
금방 익숙해질겁니다.
아 그리고 빼먹을뻔 했네요
만약에 구조체에 int a; double b; int c; 가있다면
int(4)pad(4)double(8)int(4) 해서 끝이 아닙니다.
만약 이게 배열로 선언이 된다면 짝수인덱스 번째의 double은 4의 경계에 서게 될거니까요...
나머지가 4가 남기때문에 이렇게 하지 않습니다.
마지막에 끝나는 메모리가 가장 큰 변수의 크기의 경계에 걸치게 패딩을 넣어줍니다
그럼 int(4)pad(4)double(8)int(4)pad(4)가 되겠습니다.
하지만 우린 서버개발자니까...
패킷의 경우에는 패딩을 0으로 만들고 개발할겁니다 왜냐구요?
컴파일러마다.. 패딩의 규칙이 달라질 수도 있거든요.
우린 전처리기를 사용해서 패딩을 0으로 만들어 버리고 갈겁니다!
그리고 패딩을 0으로 만든다는 개념은 곧 직렬화랑 연결이 됩니다.
그 직렬화 패킷을 통해서 우리는 데이터를 주고 받을거니까요...
이건 아직 시작에 불과합니다.
다들 열심히 공부해 보자구요
#pragma pack(1) #pragma pack()
ㅋㅋㅋㅋ 이렇게 보니까 별거없죠? 오늘도 긴글 읽어주셔서 감사드리구요.
다음엔 더 좋은 정보를 정리해 둘테니까...
필요하시다면 얼마든지 같이 공부하도록해요!!
그럼 감사합니다
안녕히계세요
'c,c++ 기본 > next step' 카테고리의 다른 글
메모리에 대한 얘기 (0) | 2021.11.07 |
---|---|
바이트 패딩과 캐시에 대한 얘기 (0) | 2021.11.06 |
함수에 대한 얘기 (0) | 2021.11.01 |
연산자와 조건문에 대한 얘기 (4) | 2021.10.29 |
변수와 타입, 키워드에관한 얘기 (2) | 2021.10.28 |