01.Build Introduction
# 과정
Pre Processor 과정부터 Linker 과정 까지를 Compile이라고 부른다
# 대표 컴파일러 종류
Visual C++
gcc
clang
02.Header file
선언부는 헤더 파일에
구현부는 cpp파일에 넣어주는 것이 일반적이다
03.PreProcessor
전처리 과정에서는
#include, #define, #ifdef, #pragma once와 같은
#을 사용한 부분들을 복사 & 붙여 넣기를 통해 처리한다
여러 파일들이 생성되고
서로 헤더파일을 참조하다 보면,
전처리 과정에서 include 중복이 발생하는데 이를 막아주기 위해서는
모든 헤더파일 위에 #pragma once를 작성하면 된다
C++에서는 최대한 define 문법을 사용하지 않는 것이 좋다
define을 대체하는 방법으로는
constexpr과 if constexpr
혹은 C++ STL <limits>를 활용하는 방법이 있으며 아래와 같다
#include <iostream>
#include <limits>
//#define MAX_UINT16 65535
//#define MAX(a, b) (((a) > (b)) ? (a) : (b))
//#define ABCD 2
constexpr int ABCD = 2;
int main()
{
std::cout << std::numeric_limits<uint16_t>::max() << std::endl;
std::cout << std::max(10, 100) << std::endl;
if constexpr (ABCD)
{
std::cout << "Y";
}
else
{
std::cout << "N";
}
}
04. Extern & Static
Extern은
외부에 선언되어 있음을 알려주는 것이고
함수는 extern이 암시적으로 생략되어 있다
Static은
함수 혹은 변수가 해당 파일 혹은 해당 트랜스레이션 유닛 밖에서
사용하지 못하도록 막기 위해 사용한다
05. Assembly
1) 곱셈보다 비트 시프트를 쓰면 속도가 빠르다는 말은 거짓이다 (똑같다)
unsigned int num8x(unsigned int a)
{
return a * 8;
}
unsigned int num8x_2(unsigned int a)
{
return a << 3;
}
2) 연산 처리 속도는 라인 수와 무조건 비례하진 않는다
int divide(int a, int b)
{
return a / b;
}
int divideBy13(int a)
{
return a / 13;
}
idiv가 imul보다 5배 정도 클럭이 더 필요하다
그래서 실제 라인 수는 아래가 더 길지만
시간은 아래가 더 빨리 처리된다
3) 컴파일러마다 효율이 다르다
int test (int a)
{
if (a == 0) return a * 0;
else if (a == 1) return a;
else if (a == 2) return a / 4;
else if (a == 3) return 5;
return 0;
}
int test2 (int a)
{
switch (a)
{
case 0: return a * 0;
case 1: return a;
case 2: return a / 4;
case 3: return 5;
default: return 0;
}
}
06. Debug
디버그를 할 때는
Variable, Break Pointer, Call Stack (Back trace)을 적극 활용하면 된다
07. Library
1) Header Only Library
#include 헤더를 추가하고 있는 라이브러리라면
모두 이렇게 부를 수 있다
2) Static Library
.lib, .a 확장자이고
Link time에 바인딩이 된다
3) Dynamic Library
.dll, .so 확장자이고
Load time이나 Rutime에 바인딩이 된다
Load time은 프로그램이 실행되고 Loader가 프로세스를 실행시킬 때, 같이 바인딩되고
Rutime은 실행 도중에 바인딩된다
# Static Library
obj파일들을 하나의 파일인 static library file로 만들고 이를 아카이브라고 부른다
그리고 해당 static library file에서 필요한 부분들만 선택 및 재배치하고
링크 과정을 거친다
결국은 static library는 obj파일들을 모은 하나의 파일에 불과하다
08. Dynamic Library (Shared Library)
Dynamic Library는 Load time 혹은 Runtime에 바인딩이 된다
1) Load time
오브젝트 파일을 만들 때, fPIC라는 옵션을 설정해 주면
PIC로 설정된 오브젝트 파일들을 모아서
하나의 Dynamic Library를 만들게 된다
그리고 실행파일에서는 Dynamic Library의 내용은 포함되지 않고
Load time에 해당 Dynamic Library가 바인드 된다
PIC(Position Independent Code)는
absolute base address를 relative base address로 바꿔서
각각의 다른 애플리케이션들에서 다이나믹 라이브러리와 그 코드들이 로드될 수 있다
2) Runtime
코드(실행 과정)에서 라이브러리를 오픈하고
심볼과 function을 로드하면서 실행시키는 방법으로 가능하며
아래에 있는 코드는 이에 대한 예시 코드이다
다이나믹 라이브러리가 필요한 대표적인 경우는
실행파일을 이미 가지고 있고
실행파일을 업데이트해 주기 위해서 다이나믹 라이브러리만 바꿔주고
실행파일의 재빌드 과정을 하지 않아도 되는 경우에 사용한다
#include <dlfcn.h>
#include <iostream>
int main()
{
void * handle = dlopen("./libfoo.so", RTLD_LAZY);
if(!handle)
{
std::cout << "No library" << std::endl;
return 1;
}
void (*fooPtr)();
fooPtr = (void(*)())dlsym(handle,"_Z3foov");
const char* dlsym_error = dlerror();
if(dlsym_error)
{
std::cout << "No symbol" << std::endl;
return 1;
}
(*fooPtr)();
dlclose(handle);
return 0;
}
09. constexpr
런타임 중에 처리되는 연산을 컴파일 시에 처리하게 하는 문법이다
컴파일할 때, 미리 연산에 필요한 데이터들을 다 알고 있는 경우에만 사용 가능하다
#include <iostream>
class Square
{
public:
constexpr Square(int l)
: mL{ l }
{
}
constexpr int area() const
{
return mL * mL;
}
constexpr int perimeter() const
{
return mL * 4;
}
private:
int mL;
};
int main()
{
constexpr Square c{ 10 };
int a = c.area();
int b = c.perimeter();
// 함수들이 호출되지 않고 바로 결과값이 도출
}
10. Attributes
# nodiscard
함수의 반환 값을 강제하는 역할을 한다
#include <iostream>
[[nodiscard]]
int fn()
{
// do some work
return 0;
}
int main()
{
//fn();// 빨간줄 생김
int a = fn();
}
# deprecated
작업을 하다가 함수를 바로 제거하는 것은 위험하기 때문에
삭제가 된 함수를 사용하면, 오류가 발생한다
#include <iostream>
[[deprecated("fn() is deprecatd")]]
int fn()
{
// do some work
return 0;
}
int main()
{
fn();
}
# fallthrough
switch문에서 case에 반드시 break를 작성해야 하고
만약 break를 작성하지 않는다면, 오류로 추적을 한다
하지만 의도적으로 fallthrough를 하고 오류 추적이 불필요하다면
해당 attribute를 사용하면 된다
#include <iostream>
int main()
{
int a = 1;
switch (a)
{
case 1:
std::cout << "1" << std::endl;
break;
case 2:
std::cout << "1" << std::endl;
[[fallthrough]];
case 3:
std::cout << "1" << std::endl;
break;
default:
std::cout << "default" << std::endl;
break;
}
}
# maybe_unused
변수나 함수의 리턴 값이나 매개변수가 사용되지 않더라도 Warning이 발생하지 않도록 한다
[[maybe_unused]] int a = 10;
// x 변수가 사용되지 않아도 컴파일러 경고를 방지합니다.
[[maybe_unused]] void unusedFunction(int param)
{
// param 매개 변수가 사용되지 않아도 컴파일러 경고를 방지합니다.
}
11. Unit Test
유닛테스트는 구현된 부분을 테스트하며
버그를 수정하여 로직의 완성도를 높이는 것이다
유닛테스트의 장점은
초기 단계에서 버그를 잡을 수 있고
샘플 코드의 역할을 한다
또한, 유지 보수를 쉽게 만들어준다
C++에서 Unit Test를 하기 위해서는
Google Test를 이용하는 방법이 있다
이는 빌드해서 바이너리를 실행해야 테스트가 가능하다는 복잡함이 있다
12. Static code analysis
Static code analysis를 이용하면
빌드나 실행을 할 때 오류나 경고와 관련된 테스트 및 분석은 당연히 가능하고
컴파일 과정 없이 코드에 문제가 있는지도 확인이 가능하다
무료 Static code analysis 중에는 Cppcheck가 있다
13. Code Formetter
작성한 코드들을 특정한 코드 스타일로 변경해 줄 때 사용한다
IDE에서는 우리의 스타일 대로 작성하더라도
협업을 하는 경우에는 스탠더드 한 형태로 바꿔주는 코드 포매터를 이용한다
또한, 회사에서는 지정한 형태의 코드 포매터를 이용한다
커밋을 할 때 자동으로 코드 포매터가 적용되게 하려면 git hook을 이용하면 된다
'C++ > [노코프] C++' 카테고리의 다른 글
[C++ NOTE] Smart Pointer (0) | 2024.03.29 |
---|---|
[C++ NOTE] Inheritance (0) | 2024.03.28 |
[C++ NOTE] OOP (0) | 2024.03.27 |
[C++ NOTE] Resource Move (0) | 2024.03.26 |
[C++ NOTE] Memory Structure (0) | 2024.03.24 |