본문 바로가기
임베디드 SW/C(C++)

코딩 스타일 (C++)

by MachineJW 2025. 3. 4.
C++에서 코딩 스타일의 규약은 가독성, 유지보수성, 성능, 안정성을 고려한다.
특히, 임베디드 시스템에서는 메모리 제어와 성능 최적화가 중요한 만큼 일부 규칙이 일반적인 C++보다 더 엄격할 수 있다. 가독성과 일관성을 위한 규칙일뿐 반드시 지켜져야하는 부분은 아니나, 프로젝트를 누군가와 협업한다면 규칙을 지키는 것을 권장한다.

1. 네이밍 규칙 (Naming Conventions)

  • 로컬 변수 : 소문자로 시작, 언더바 _ 로 단어 구분
int sensor_value; // _ 구분
float temperature_celsius; // 카멜표기법

 

  • 멤버 변수 : m_ 접두사 사용
class Sensor {
private:
    int m_value;
};
  • 전역 변수 : g_ 접두사 사용
int g_system_status;
  • 함수명 : 대문자로 시작, 카멜표기법 사용, Get과 Set은 정확한 명칭을 표기할 것
int GetSensorValue();
void ResetSystem();

class Device {
public:
    int GetTemperature(); // Get
    void SetTemperature(int temp); // Set
};
  • 상수 및 매크로 : const 또는 constexpr 사용, 매크로를 사용할 경우 전체 대문자, 언더바_구분
constexpr int MAX_SENSOR_COUNT = 10;
#define MAX_BUFFER_SIZE 256 // 전체 대문자 표기
  • 클래스 & 구조체명 : PascalCase 사용
class MotorController;
struct SensorData;

2.  선언과 문장 순서

  • C++에서 선언들을 코드에 있는 문장에 혼합해서 사용할 수 있게 허용한다.
  • 구문론적 편의는 제공하지만 개발자들은 코드의 가독성과 유지보수성에 미치는 영향을 고려해 주의해서 사용한다.
함수 블록 시작에서 주요 변수 선언을 먼저 수행→ 핵심적인 변수(int, float 등)는 함수 시작 부분에서 선언.
루프나 특정 코드 블록에서만 필요한 변수는 해당 블록에서 선언→ 사용 범위를 제한하여 코드 가독성을 높임.
불필요한 선언을 최소화하고, 필요할 때 선언→ 선언과 동시에 초기화할 수 있는 경우 그렇게 작성.
#include <iostream>

void process() {
    int a = 10;
    
    std::cout << "Value of a: " << a << std::endl;
    
    int b = 20;  // 문장 사이에서 선언 가능 (C++만 가능)
    std::cout << "Sum: " << (a + b) << std::endl;
}
/************************************************************/
void process() {
    std::cout << "Processing...\n";

    {
        int temp = 42;  // 블록 내부에서만 유효한 변수
        std::cout << "Temp value: " << temp << std::endl;
    }  // 여기서 temp는 소멸됨

    // std::cout << temp;  // 오류 발생 (temp는 블록 범위를 벗어남)
}
/************************************************************/
#include <iostream>

void loop_example() {
    for (int i = 0; i < 5; i++) {  // 루프 내에서 선언
        std::cout << "Iteration " << i << std::endl;
    }
    // i는 여기서 사라짐 (더 이상 접근 불가능)
}

3.  포인터(Pointer) vs 참조자(Reference)

C++에서 포인터와 참조자는 둘 다 메모리 주소를 활용하여 객체를 가리키는 기능을 하지만, 사용 방식과 동작 방식이 다름.
임베디드 시스템에서는 메모리 효율성과 성능이 중요한 만큼, 포인터와 참조자를 적절히 사용하는 것이 중요.

2.1 포인터 (Pointer)

 

  • 포인터는 메모리 주소를 저장하는 변수.
  • 특정 객체를 가리키거나, nullptr을 가질 수 있음.
  • 동적으로 할당된 메모리를 가리킬 수 있음.
  • 포인터는 항상 일정한 크기 (대부분의 32-bit 시스템에서는 4바이트).
  • 하지만, 가리키는 데이터 크기는 다를 수 있음.
int value = 10;
int* ptr = &value; // 포인터는 변수의 주소를 저장
std::cout << *ptr; // 10 (역참조)
int* ptr = nullptr // 포인터는 어떠한 것도 가리키지 않을 수 있음. (널 포인터)
int* ptr = new int(5); // 동적 할당 가능
delete ptr;            // 할당 해제 (필수)
int arr[3] = {1, 2, 3}; // 여러개의 주소 조작 가능
int* p = arr; // 배열의 첫 번째 요소를 가리킴
std::cout << *(p + 1); // 2 (포인터 연산 가능)

 

2.2 참조자 (Reference)

 

  • 참조자는 어떤 변수에 대한 별칭(alias) 역할.
  • 선언 시 반드시 초기화해야 하고, 이후 다른 변수로 변경할 수 없음.
  • nullptr을 가질 수 없음.
  • 참조자는 nullptr이 될 수 없으므로, 널 체크 필요 없음.
  • 직접적인 메모리 조작이 필요 없는 경우 포인터보다 안전함.
  • 참조자를 사용하면 값이 복사되지 않으므로 성능 향상 효과
int value = 10;
int& ref = value; // 참조자는 원본을 직접 가리킴
ref = 20;         // value도 20으로 변경됨
int& ref = nullptr; // 불가능 (참조자는 항상 유효한 객체를 가리켜야 함)
void Modify(int& x) {
    x += 10;
}

int value = 5;
Modify(value); // value가 15로 변경됨

 

 

NULL 가능 여부 가능 (nullptr 가능) 불가능
선언 후 변경 가능 여부 다른 객체를 가리킬 수 있음 변경 불가능 (처음 가리킨 객체만 유지)
초기화 필요 여부 선택 (초기화 없이 선언 가능) 반드시 초기화 필요
메모리 주소 조작 가능 (주소 이동, 연산 가능) 불가능
배열과의 사용 배열을 가리킬 수 있음 불가능 (배열 참조 불가)
사용 목적 동적 할당, 배열, 연산 함수 매개변수, 안정적인 별칭
안전성 상대적으로 낮음 (NULL 체크 필요) 상대적으로 높음

 

 

  • 안정성과 유지보수성이 중요하다면 참조자(&)를 사용.
  • 동적 할당, NULL 체크, 메모리 주소 연산이 필요하다면 포인터(*)를 사용.
  • 임베디드 환경에서는 스택 기반(static) 할당이 선호되므로, 참조자를 우선적으로 고려하되, 필요한 경우 포인터를 활용.