ROS 2를 제대로 다루기 위해서는 C++ 문법에 대한 기본기가 필요합니다. ROS 2의 핵심 클라이언트 라이브러리인 rclcpp는 C++ 기반으로 작성되어 있으며, 실제 로봇 제어, 센서 처리, 드론 제어, 실시간 데이터 처리, 메시지 송수신 코드를 작성할 때 C++ 문법을 자연스럽게 사용하게 됩니다.
특히 ROS 2에서는 단순히 문법을 외우는 것보다 객체지향 구조, 포인터와 참조, 스마트 포인터, 클래스, 콜백 함수, 람다 함수, 템플릿, 네임스페이스, 빌드 구조를 이해하는 것이 중요합니다. 이 문법들은 ROS 2 코드에서 거의 매번 등장합니다.
1. C와 C++의 차이를 먼저 이해해야 합니다
C와 C++은 비슷해 보이지만 개발 방식에는 큰 차이가 있습니다.
C 언어는 절차지향 언어입니다. 프로그램을 함수 중심으로 구성하고, 데이터를 구조체로 묶어 사용합니다. 반면 C++은 C의 기능을 포함하면서도 클래스, 객체, 상속, 다형성, 템플릿, 스마트 포인터 같은 기능을 추가한 언어입니다.
로봇 개발에서는 아직도 C 언어가 많이 사용됩니다. 특히 MCU 펌웨어, 모터 제어, 센서 드라이버, 임베디드 제어 코드에서는 C 언어가 자주 사용됩니다.
반면 ROS 2의 상위 제어 로직, 경로 계획, 상태 관리, 센서 데이터 처리, 노드 구성 등은 C++로 작성하는 경우가 많습니다.
따라서 ROS 2 개발자는 C 문법도 어느 정도 이해해야 하고, C++ 문법도 익숙해야 합니다. 단순히 “C++만 알면 된다”가 아니라, C의 메모리 구조와 C++의 객체지향 구조를 함께 이해하는 것이 좋습니다.
2. C/C++ 실행환경 구성
a. 기본 개발 도구 확인하기
먼저 C/C++ 컴파일러와 빌드 도구가 설치되어 있는지 확인합니다.
터미널을 열고 다음 명령어를 입력합니다.
gcc --version
g++ --version
cmake --version
make --version
정상적으로 버전 정보가 출력되면 기본 도구가 설치되어 있는 것입니다.
만약 설치되어 있지 않다면 다음 명령어로 설치합니다.
sudo apt update
sudo apt install -y build-essential cmake gdb
각 도구의 역할은 다음과 같습니다.
gcc는 C 언어 컴파일러입니다.
g++는 C++ 컴파일러입니다.
cmake는 C/C++ 프로젝트 빌드 설정 도구입니다.
make는 실제 빌드 명령을 실행하는 도구입니다.
gdb는 C/C++ 디버깅 도구입니다.
b. 실습 폴더 만들기
먼저 C/C++ 문법 실습용 폴더를 만듭니다.
mkdir -p ~/cpp_practice
cd ~/cpp_practice
VSCode에서 해당 폴더를 열려면 다음 명령어를 사용합니다.
code .
이제 이 폴더 안에서 실습 파일을 만들면 됩니다.
c. 가장 간단한 C 프로그램 실습
먼저 C 언어 실습 파일을 하나 만듭니다.
파일 이름은 hello.c로 합니다.
#include <stdio.h>
int main() {
printf("Hello C\n");
return 0;
}
터미널에서 컴파일합니다.
gcc hello.c -o hello_c
실행합니다.
./hello_c
결과는 다음과 같습니다.
Hello C

여기서 반드시 이해해야 할 부분은 다음입니다.
gcc hello.c -o hello_c
이 명령어는 hello.c 파일을 컴파일해서 hello_c라는 실행 파일을 만들라는 뜻입니다.
./hello_c
이 명령어는 현재 폴더에 있는 hello_c 실행 파일을 실행하라는 뜻입니다.
d. 가장 간단한 C++ 프로그램 실습
이번에는 C++ 파일을 만들어 봅니다.
파일 이름은 hello.cpp로 합니다.
#include <iostream>
int main() {
std::cout << "Hello C++" << std::endl;
return 0;
}
컴파일합니다.
g++ hello.cpp -o hello_cpp
실행합니다.
./hello_cpp
결과는 다음과 같습니다.
Hello C++
C 파일은 gcc로 컴파일하고, C++ 파일은 g++로 컴파일합니다.
정리하면 다음과 같습니다.
gcc hello.c -o hello_c
g++ hello.cpp -o hello_cpp

3. 기본 자료형
프로그래밍에서 자료형은 데이터를 저장하는 방식입니다. C/C++에서는 변수마다 자료형을 지정해야 합니다.
대표적인 기본 자료형은 다음과 같습니다.
int age = 20;
float temperature = 36.5f;
double position = 12.345678;
char grade = 'A';
bool is_active = true;
int는 정수, float와 double은 실수, char는 문자, bool은 참 또는 거짓을 저장합니다.
로봇 개발에서는 자료형 선택이 중요합니다. 예를 들어 모터 속도, 센서 값, 좌표, 각도, 시간, 상태값 등을 저장할 때 적절한 자료형을 선택해야 합니다.
int motor_rpm = 1500;
double x_position = 1.25;
double yaw_angle = 0.785;
bool emergency_stop = false;
초보자들이 자주 하는 실수 중 하나는 모든 숫자를 무조건 int로 처리하는 것입니다. 하지만 위치, 속도, 각도, 시간처럼 소수점이 필요한 값은 float 또는 double을 사용해야 합니다.
일반적으로 PC나 ROS 2 환경에서는 정밀도가 높은 double을 많이 사용합니다. 반면 MCU 같은 임베디드 환경에서는 성능과 메모리 때문에 float를 사용하는 경우도 많습니다.
4. 변수와 상수
변수는 값이 바뀔 수 있는 저장 공간입니다.
int speed = 100;
speed = 150;
상수는 한 번 정하면 바뀌면 안 되는 값입니다. C++에서는 const를 사용합니다.
const double PI = 3.141592;
const int MAX_SPEED = 3000;
로봇 개발에서는 상수를 적극적으로 사용하는 것이 좋습니다. 예를 들어 최대 속도, 바퀴 반지름, 기어비, 제어 주기 같은 값은 코드 중간에 직접 숫자로 쓰는 것보다 상수로 정의하는 것이 안전합니다.
좋지 않은 예시는 다음과 같습니다.
double distance = rpm * 0.034 * 3.141592;
이 코드는 0.034가 무엇을 의미하는지 바로 알기 어렵습니다.
더 좋은 예시는 다음과 같습니다.
const double WHEEL_RADIUS = 0.034;
const double PI = 3.141592;
double distance = rpm * WHEEL_RADIUS * PI;
이렇게 작성하면 코드의 의미가 훨씬 분명해집니다.
5. 연산자
C/C++에서는 다양한 연산자를 사용합니다.
산술 연산자는 다음과 같습니다.
int a = 10;
int b = 3;
int sum = a + b;
int sub = a - b;
int mul = a * b;
int div = a / b;
int mod = a % b;
여기서 주의할 점은 정수끼리 나누면 결과도 정수라는 점입니다.
int a = 10;
int b = 3;
double result = a / b;
이 경우 result는 3.3333이 아니라 3.0이 됩니다. 왜냐하면 a / b가 먼저 정수 연산으로 처리되기 때문입니다.
정확한 실수 결과를 원하면 다음처럼 작성해야 합니다.
double result = static_cast<double>(a) / b;
ROS 2나 로봇 제어 코드에서는 속도, 거리, 각도, 시간 계산이 많기 때문에 정수 나눗셈 실수는 반드시 조심해야 합니다.
아래의 예제 소스를 작성하고 컴파일하여 실행결과를 확인하시기 바랍니다.
파일 이름은 01_variable.cpp입니다.
#include <iostream>
#include <string>
int main() {
int motor_rpm = 1500;
double battery_voltage = 12.5;
bool is_motor_on = true;
std::string robot_name = "mobile_robot";
std::cout << "Robot name: " << robot_name << std::endl;
std::cout << "Motor RPM: " << motor_rpm << std::endl;
std::cout << "Battery voltage: " << battery_voltage << std::endl;
std::cout << "Motor on: " << is_motor_on << std::endl;
return 0;
}
컴파일합니다.
g++ -std=c++17 01_variable.cpp -o 01_variable
./01_variable
이 실습에서는 변수, 자료형, 문자열, 출력문을 함께 익힐 수 있습니다.

6. 조건문
조건문은 특정 조건에 따라 코드를 다르게 실행할 때 사용합니다.
int battery = 30;
if (battery < 20) {
// 배터리 부족
} else if (battery < 50) {
// 배터리 주의
} else {
// 배터리 정상
}
로봇 개발에서는 조건문이 매우 많이 사용됩니다.
예를 들어 다음과 같은 상황에서 사용합니다.
if (emergency_stop == true) {
motor_speed = 0;
}
또는 다음처럼 간단히 작성할 수 있습니다.
if (emergency_stop) {
motor_speed = 0;
}
bool 변수는 이미 참 또는 거짓을 의미하므로 == true를 생략해도 됩니다.
조건이 복잡해질 때는 논리 연산자를 사용합니다.
if (battery_low && gps_ok) {
// 배터리는 낮지만 GPS는 정상
}
if (obstacle_detected || emergency_stop) {
// 장애물이 있거나 비상 정지 상태
}
&&는 AND, ||는 OR, !는 NOT을 의미합니다.
if (!sensor_ready) {
// 센서가 준비되지 않았을 때
}
아래의 예제 소스를 작성하고 컴파일하여 실행결과를 확인하시기 바랍니다.
파일 이름은 02_if.cpp입니다.
#include <iostream>
int main() {
double battery_voltage = 10.8;
if (battery_voltage < 10.5) {
std::cout << "Battery is low" << std::endl;
} else if (battery_voltage < 11.5) {
std::cout << "Battery warning" << std::endl;
} else {
std::cout << "Battery normal" << std::endl;
}
return 0;
}
컴파일과 실행은 다음과 같습니다.
ROS 2 Humble의 C++ 코드는 일반적으로 C++14 이상 문법을 사용하며, 실습에서는 C++17 기준으로 공부해도 괜찮습니다.
C++17 옵션을 붙여 컴파일하려면 다음처럼 작성합니다.
g++ -std=c++17 hello.cpp -o hello_cpp
앞으로 C++ 실습에서는 다음 명령어를 기본으로 사용하면 좋습니다.
g++ -std=c++17 파일이름.cpp -o 실행파일이름
g++ -std=c++17 02_if.cpp -o 02_if
./02_if
battery_voltage 값을 직접 바꿔 보면서 출력결과를 확인하시기 바랍니다.
예를 들어 다음 값으로 바꿔 봅니다.
double battery_voltage = 9.8;
double battery_voltage = 10.8;
double battery_voltage = 12.2;
값에 따라 출력 결과가 달라지는 것을 확인하면 조건문을 쉽게 이해할 수 있습니다.

7. 반복문
반복문은 같은 작업을 여러 번 수행할 때 사용합니다.
대표적으로 for, while, do-while이 있습니다.
for (int i = 0; i < 10; i++) {
// 10번 반복
}
배열이나 벡터의 데이터를 처리할 때 많이 사용합니다.
double sensor_values[5] = {1.0, 2.0, 3.0, 4.0, 5.0};
for (int i = 0; i < 5; i++) {
double value = sensor_values[i];
}
while문은 조건이 참인 동안 계속 반복합니다.
while (is_running) {
// 실행 중일 때 계속 반복
}
로봇 프로그램에서는 무한 반복 구조를 자주 볼 수 있습니다. 하지만 ROS 2에서는 일반적인 while(true) 반복보다는 콜백, 타이머, executor 구조를 많이 사용합니다. 그래도 C/C++ 기본 반복문을 이해해야 ROS 2 내부 동작과 일반 제어 루프를 이해할 수 있습니다.
아래의 예제 소스를 작성하고 컴파일하여 실행결과를 확인하시기 바랍니다.
파일 이름은 03_for.cpp입니다.
#include <iostream>
int main() {
for (int i = 0; i < 5; i++) {
std::cout << "Control loop count: " << i << std::endl;
}
return 0;
}
컴파일합니다.
g++ -std=c++17 03_for.cpp -o 03_for
./03_for
로봇 제어에서는 반복문을 제어 루프와 연결해서 설명하면 좋습니다.

8. 배열
배열은 같은 자료형의 데이터를 여러 개 저장하는 구조입니다.
int numbers[5] = {1, 2, 3, 4, 5};
배열의 인덱스는 0부터 시작합니다.
int first = numbers[0];
int second = numbers[1];
초보자가 자주 하는 실수는 배열 범위를 넘어 접근하는 것입니다.
int numbers[5] = {1, 2, 3, 4, 5};
int wrong = numbers[5];
위 코드는 잘못된 코드입니다. 배열 크기가 5이면 접근 가능한 인덱스는 0부터 4까지입니다.
C 스타일 배열은 빠르고 단순하지만, 크기 관리가 불편합니다. C++에서는 보통 std::array나 std::vector를 더 많이 사용합니다.
아래의 예제 소스를 작성하고 컴파일하여 실행결과를 확인하시기 바랍니다.
04_array.cpp 파일에 다음 코드를 작성합니다.
배열의 값은 인덱스를 사용해서 변경할 수 있습니다.
motor_rpm[0] = 1500;
다음 예제를 작성해 보겠습니다.
#include <iostream>
int main() {
int motor_rpm[4] = {1200, 1350, 1280, 1420};
std::cout << "Before update" << std::endl;
for (int i = 0; i < 4; i++) {
std::cout << "Motor " << i + 1 << " RPM: "
<< motor_rpm[i] << std::endl;
}
motor_rpm[0] = 1500;
motor_rpm[1] = 1500;
std::cout << std::endl;
std::cout << "After update" << std::endl;
for (int i = 0; i < 4; i++) {
std::cout << "Motor " << i + 1 << " RPM: "
<< motor_rpm[i] << std::endl;
}
return 0;
}
컴파일하고 실행합니다.
g++ -std=c++17 04_array.cpp -o 04_array
./04_array
실행 결과는 다음과 비슷합니다.
Before update
Motor 1 RPM: 1200
Motor 2 RPM: 1350
Motor 3 RPM: 1280
Motor 4 RPM: 1420
After update
Motor 1 RPM: 1500
Motor 2 RPM: 1500
Motor 3 RPM: 1280
Motor 4 RPM: 1420
이 실습을 통해 배열의 특정 위치에 있는 값을 직접 바꿀 수 있다는 것을 확인할 수 있습니다.

9. std::vector
std::vector는 C++에서 가장 많이 사용하는 동적 배열입니다. 크기를 자동으로 조절할 수 있습니다.
#include <vector>
std::vector<int> numbers;
numbers.push_back(10);
numbers.push_back(20);
numbers.push_back(30);
값을 읽을 때는 배열처럼 접근할 수 있습니다.
int value = numbers[0];
전체 데이터를 반복할 때는 다음처럼 작성할 수 있습니다.
for (int i = 0; i < numbers.size(); i++) {
int value = numbers[i];
}
더 현대적인 C++ 문법으로는 range-based for문을 사용합니다.
for (int value : numbers) {
// value 사용
}
값을 복사하지 않고 참조로 받고 싶다면 다음처럼 작성합니다.
for (const auto& value : numbers) {
// value 읽기 전용 사용
}
ROS 2 개발에서는 여러 개의 좌표, 센서값, 경로점, 객체 목록 등을 다룰 때 std::vector를 자주 사용합니다. 초중급자는 반드시 익숙해져야 합니다.
아래의 예제 소스를 작성하고 컴파일하여 실행결과를 확인하시기 바랍니다.
파일 이름은 05_vector.cpp입니다.
#include <iostream>
#include <vector>
int main() {
std::vector<double> sensor_values;
sensor_values.push_back(1.2);
sensor_values.push_back(2.5);
sensor_values.push_back(3.1);
for (const auto& value : sensor_values) {
std::cout << "Sensor value: " << value << std::endl;
}
return 0;
}
컴파일합니다.
g++ -std=c++17 05_vector.cpp -o 05_vector
./05_vector
std::vector는 ROS 2 개발에서도 매우 자주 사용됩니다. 센서값 목록, 경로점 목록, 좌표 목록 등을 저장할 때 사용하기 좋습니다.

10. 문자열
C 언어에서는 문자열을 char 배열로 다룹니다.
char name[] = "robot";
C++에서는 보통 std::string을 사용합니다.
#include <string>
std::string robot_name = "mobile_robot";
문자열을 합칠 수도 있습니다.
std::string frame_id = "base_link";
std::string topic_name = "/robot/" + frame_id;
단, 문자열 리터럴끼리 바로 더하는 것은 문제가 될 수 있습니다.
std::string text = std::string("robot") + "_1";
ROS 2 개발에서는 토픽 이름, 프레임 이름, 파라미터 이름, 로그 메시지 등을 다룰 때 문자열을 많이 사용합니다. 따라서 std::string 사용법은 기본적으로 알고 있어야 합니다.
아래의 예제 소스를 작성하고 컴파일하여 실행결과를 확인하시기 바랍니다.
파일 이름은 06_string.cpp입니다.
이번에는 로봇 이름, 센서 이름, 프레임 이름을 문자열로 저장해 보겠습니다.
#include <iostream>
#include <string>
int main() {
std::string robot_name = "delivery_robot";
std::string sensor_name = "lidar";
std::string frame_id = "base_link";
std::cout << "Robot name: " << robot_name << std::endl;
std::cout << "Sensor name: " << sensor_name << std::endl;
std::cout << "Frame ID: " << frame_id << std::endl;
return 0;
}
컴파일하고 실행합니다.
g++ -std=c++17 06_string.cpp -o 06_string
./06_string
실행 결과는 다음과 비슷합니다.
Robot name: delivery_robot
Sensor name: lidar
Frame ID: base_link
이 예제에서는 문자열 변수를 여러 개 만들고 출력하는 방법을 익힐 수 있습니다.

11. 함수
함수는 반복되는 코드를 하나로 묶는 방법입니다.
int add(int a, int b) {
return a + b;
}
함수를 호출할 때는 다음처럼 사용합니다.
int result = add(3, 5);
로봇 개발에서는 계산식이나 반복 작업을 함수로 분리하는 것이 중요합니다.
double rpmToVelocity(double rpm, double wheel_radius) {
const double PI = 3.141592;
return (rpm / 60.0) * 2.0 * PI * wheel_radius;
}
이렇게 작성하면 속도 계산 코드를 여러 곳에서 재사용할 수 있습니다.
함수를 잘 나누면 코드가 짧아지고, 테스트하기 쉬워지고, 나중에 수정하기도 편해집니다.
아래의 예제 소스를 작성하고 컴파일하여 실행결과를 확인하시기 바랍니다.
파일 이름은 07_fuction.cpp입니다.
#include <iostream>
double rpmToVelocity(double rpm, double wheel_radius) {
const double PI = 3.141592;
return (rpm / 60.0) * 2.0 * PI * wheel_radius;
}
int main() {
double rpm = 1500.0;
double wheel_radius = 0.05;
double velocity = rpmToVelocity(rpm, wheel_radius);
std::cout << "Velocity: " << velocity << " m/s" << std::endl;
return 0;
}
컴파일합니다.
g++ -std=c++17 07_function.cpp -o 07_function
./07_function
이 예제는 단순한 문법 예제보다 좋습니다. 학생들이 로봇 개발에서 자주 만나는 속도 계산과 함수 개념을 같이 배울 수 있기 때문입니다.

12. 값 전달과 참조 전달
C++ 함수에서 인자를 전달하는 방식은 매우 중요합니다.
먼저 값 전달입니다.
void changeValue(int x) {
x = 100;
}
int a = 10;
changeValue(a);
이 경우 a의 값은 바뀌지 않습니다. 함수 안의 x는 a를 복사한 별도의 값이기 때문입니다.
참조 전달은 원본을 직접 다룹니다.
void changeValue(int& x) {
x = 100;
}
int a = 10;
changeValue(a);
이 경우 a는 100으로 바뀝니다.
읽기만 할 때는 const 참조를 많이 사용합니다.
void printName(const std::string& name) {
// name을 읽기만 함
}
const std::string&는 문자열을 복사하지 않고 참조하되, 함수 안에서 수정하지 못하게 합니다.
ROS 2 C++ 코드에서도 const auto&, const std::shared_ptr<...>&, const 메시지타입::SharedPtr 같은 형태가 자주 등장합니다. 그래서 참조와 const 개념은 반드시 이해해야 합니다.
13. 포인터
포인터는 메모리 주소를 저장하는 변수입니다.
int value = 10;
int* ptr = &value;
&value는 value 변수의 주소를 의미합니다. ptr은 그 주소를 저장합니다.
포인터가 가리키는 실제 값에 접근하려면 *를 사용합니다.
int data = *ptr;
포인터는 강력하지만 위험합니다. 잘못된 주소를 가리키거나 이미 해제된 메모리를 접근하면 프로그램이 비정상 종료될 수 있습니다.
int* ptr = nullptr;
nullptr은 아무것도 가리키지 않는 포인터를 의미합니다. C++에서는 예전의 NULL보다 nullptr을 사용하는 것이 좋습니다.
포인터를 사용할 때는 항상 유효한 주소인지 확인하는 습관이 필요합니다.
if (ptr != nullptr) {
int value = *ptr;
}
ROS 2에서는 일반 포인터보다 스마트 포인터를 훨씬 많이 사용합니다. 하지만 스마트 포인터를 이해하려면 먼저 일반 포인터 개념을 알고 있어야 합니다.
아래의 예제 소스를 작성하고 컴파일하여 실행결과를 확인하시기 바랍니다.
파일 이름은 08_pointer.cpp입니다.
#include <iostream>
int main() {
int motor_rpm = 1500;
int* ptr = &motor_rpm;
std::cout << "motor_rpm value: " << motor_rpm << std::endl;
std::cout << "motor_rpm address: " << &motor_rpm << std::endl;
std::cout << "ptr value: " << ptr << std::endl;
std::cout << "ptr points to value: " << *ptr << std::endl;
return 0;
}
컴파일합니다.
g++ -std=c++17 08_pointer.cpp -o 08_pointer
실행합니다.
./08_pointer
실행 결과는 다음과 비슷하게 나옵니다.
motor_rpm value: 1500
motor_rpm address: 0x7ffc1a2b3c4d
ptr value: 0x7ffc1a2b3c4d
ptr points to value: 1500
주소값은 실행 환경마다 다르게 나옵니다.

14. 구조체
구조체는 여러 데이터를 하나로 묶는 자료형입니다.
struct MotorState {
double rpm;
double current;
double temperature;
};
사용할 때는 다음처럼 작성합니다.
MotorState motor;
motor.rpm = 1500.0;
motor.current = 2.5;
motor.temperature = 45.0;
구조체는 관련 있는 데이터를 묶을 때 좋습니다.
예를 들어 로봇 위치를 다음처럼 표현할 수 있습니다.
struct Position {
double x;
double y;
double theta;
};
그리고 함수의 인자로 사용할 수 있습니다.
void printPosition(const Position& pos) {
// pos.x, pos.y, pos.theta 사용
}
C에서는 구조체가 단순히 데이터 묶음에 가깝지만, C++에서는 구조체 안에 함수도 넣을 수 있습니다. C++의 struct와 class는 기본 접근 제한자가 다르다는 점을 제외하면 매우 비슷합니다.
아래의 예제 소스를 작성하고 컴파일하여 실행결과를 확인하시기 바랍니다.
파일 이름은 09_struct.cpp입니다.
#include <iostream>
struct MotorState {
double rpm;
double current;
double temperature;
};
int main() {
MotorState motor;
motor.rpm = 1500.0;
motor.current = 2.5;
motor.temperature = 42.0;
std::cout << "Motor RPM: " << motor.rpm << std::endl;
std::cout << "Motor current: " << motor.current << std::endl;
std::cout << "Motor temperature: " << motor.temperature << std::endl;
return 0;
}
컴파일합니다.
g++ -std=c++17 09_struct.cpp -o 09_struct
실행합니다.
./09_struct
실행 결과는 다음과 같습니다.
Motor RPM: 1500
Motor current: 2.5
Motor temperature: 42

'강좌 > ROS2' 카테고리의 다른 글
| ROS 2 Python Topic 실습 : RobotStatus 메시지로 로봇 상태 주고받기 (0) | 2026.05.26 |
|---|---|
| Python 기본 타입 메시지 Publisher / Subscriber 예제 (0) | 2026.05.26 |
| 로봇 개발자를 위한 Python 기초 교육 #3 (0) | 2026.05.25 |
| C++ Service Client 작성 실습 : 모바일 로봇 긴급정지 요청 보내기 (0) | 2026.05.24 |
| ROS 2 C++ Action Server / Client 실습 #1 (0) | 2026.05.24 |