Skip to content
LIVE FEED

Blog05 — C++ Cốt Lõi

05 — C++ Cốt Lõi

Mục tiêu: Chuyển từ C sang C++ một cách có chọn lọc. Nhiều telecom stack hiện đại (một phần Wireshark, srsRAN, một số 5GC, công cụ phân tích) dùng C++. C++ cho bạn RAII (tự quản lý tài nguyên — diệt tận gốc memory leak/UAF) và STL (container/string an toàn hơn mảng C). Ta học C++ như “C có thêm công cụ an toàn”, không sa đà vào mọi ngóc ngách.


1. C++ khác C ở đâu — bức tranh lớn

Khía cạnhCC++
Quản lý bộ nhớthủ công (malloc/free)RAII + smart pointer (tự động)
Chuỗichar* + \0 (nguy hiểm)std::string (biết độ dài, tự cấp phát)
Mảng độngmalloc/realloc thủ côngstd::vector (tự lớn, kiểm tra biên với .at())
Trừu tượngstruct + function pointerclass, kế thừa, template
Lỗimã trả vềexception (và mã trả về)
Kiểuyếu hơnmạnh hơn (static_cast, nullptr, enum class)

Tư duy đúng: C++ không thay thế kiến thức C của bạn — nó xây trên đó. Mọi thứ về con trỏ, bộ nhớ, endianness vẫn nguyên giá trị. C++ chỉ thêm lớp an toàn và biểu đạt.

Biên dịch:

g++ -std=c++20 -Wall -Wextra -g -O0 -fsanitize=address,undefined main.cpp -o app

2. Những nâng cấp nhỏ mà dùng ngay

#include <iostream>
#include <cstdint>

int main() {
    uint32_t teid = 0xDEADBEEF;
    std::cout << "TEID = " << std::hex << teid << "\n";   // in kiểu C++

    int *p = nullptr;          // dùng nullptr, KHÔNG dùng NULL/0
    auto len = ntohs(x);       // auto: suy luận kiểu tự động (gọn, an toàn)

    const int N = 16;
    uint8_t buf[N]{};          // {} khởi tạo zero toàn bộ
    return 0;
}
Tính năngThay cho (C)Lợi ích
nullptrNULLAn toàn kiểu, không nhầm với số 0
autogõ kiểu dàiGọn, tránh sai kiểu
std::coutprintfType-safe (không sai format specifier)
enum classenumCó phạm vi, không ngầm chuyển int
static_cast<T>cast kiểu C (T)xTường minh, an toàn, dễ tìm
enum class Cmd : uint32_t { CER = 257, DWR = 280, DPR = 282 };
Cmd c = Cmd::CER;
// if (c == 257) ...  // LỖI biên dịch — phải static_cast, tránh so sánh nhầm

3. Reference (&) — bí danh an toàn hơn con trỏ

Reference là một tên khác cho một biến đã tồn tại — không thể null, không thể “trỏ lại” chỗ khác.

void inc(uint32_t &x) { x++; }      // x là reference tới biến gốc

uint32_t v = 5;
inc(v);                              // v thành 6 — KHÔNG cần &v như con trỏ

// const reference: truyền dữ liệu lớn mà không copy, lại không cho sửa
void process(const std::vector<uint8_t> &packet);   // không copy 1500 byte, read-only

| | Con trỏ T* | Reference T& | |–|————–|—————-| | Có thể null? | Có | Không | | Trỏ lại được? | Có | Không | | Cú pháp dùng | *p, p-> | như biến thường | | Dùng khi | có thể “không có gì”, số học con trỏ | luôn có giá trị, tham số/return |

Khuyến nghị: trong C++, ưu tiên const T& cho tham số lớn (read-only) và T& cho output. Dùng con trỏ khi giá trị có thể vắng mặt (hoặc khi giao tiếp với API C).


4. Class & OOP — đóng gói dữ liệu + hành vi

#include <cstdint>
#include <vector>

class DiameterMessage {
private:                              // ẩn — chỉ trong class truy cập được
    uint32_t command_code_;
    std::vector<uint8_t> raw_;        // dữ liệu thô, tự quản lý bộ nhớ

public:
    // Constructor — gọi khi tạo object
    DiameterMessage(uint32_t code, std::vector<uint8_t> raw)
        : command_code_(code), raw_(std::move(raw)) {}   // member init list

    // Method
    uint32_t command_code() const { return command_code_; }   // const = không sửa object
    size_t size() const { return raw_.size(); }
};

DiameterMessage msg(257, {0x01, 0x00, 0x00, 0x14});
std::cout << msg.command_code() << "\n";   // 257

Khái niệm cốt lõi:

  • private/public/protected: kiểm soát truy cập (encapsulation). Field nội bộ để private.
  • Constructor / Destructor: hàm chạy khi tạo / hủy object. Destructor là chìa khóa của RAII (mục 6).
  • const method: cam kết “không sửa object” — vừa an toàn vừa tự tài liệu.
  • Quy ước đặt tên: nhiều codebase dùng hậu tố _ cho member private (raw_).

Kế thừa & hàm ảo (đa hình) — mô hình “một protocol, nhiều message type”

class Message {
public:
    virtual ~Message() = default;             // destructor ảo — BẮT BUỘC cho base class
    virtual void parse(const uint8_t *b, size_t n) = 0;  // = 0: thuần ảo (abstract)
    virtual const char *name() const = 0;
};

class CER : public Message {
public:
    void parse(const uint8_t *b, size_t n) override { /* ... */ }
    const char *name() const override { return "CER"; }
};

Liên hệ an ninh: đa hình hiện thực bằng vtable (bảng con trỏ hàm ẩn trong mỗi object). Đây là mục tiêu của vtable hijacking / vtable injection — một kỹ thuật khai thác C++ kinh điển. Hiểu vtable giúp bạn vừa khai thác (lab) vừa hiểu mitigations (vtable verification, CFI). Ta nhắc lại ở 09.


5. STL Containers — thay thế an toàn cho mảng/buffer C

5.1. std::vector<uint8_t> — buffer động, BẠN SẼ DÙNG NHIỀU NHẤT

#include <vector>
std::vector<uint8_t> packet;        // buffer rỗng, tự lớn
packet.push_back(0x01);             // thêm byte
packet.resize(20);                  // đổi kích thước
packet.size();                      // độ dài (LUÔN biết, khác mảng C!)
packet.data();                      // con trỏ uint8_t* tới dữ liệu thô (để gọi API C/socket)

uint8_t b = packet[3];              // truy cập KHÔNG kiểm tra biên (nhanh)
uint8_t b = packet.at(3);           // truy cập CÓ kiểm tra biên (ném exception nếu vượt)

Vì sao quan trọng cho security: std::vector biết độ dài của mình và .at() kiểm tra biên. Phần lớn buffer overflow của C biến mất khi dùng vector đúng cách. Khi nhận dữ liệu từ socket, vector<uint8_t> là lựa chọn mặc định. .data() + .size() cho bạn cặp (ptr, len) để giao tiếp với hàm C.

5.2. Các container khác & khi nào dùng

#include <string>          // std::string  — chuỗi an toàn (biết độ dài, tự cấp phát)
#include <vector>          // std::vector  — mảng động → buffer, danh sách AVP
#include <map>             // std::map     — cây cân bằng (key→value có thứ tự)
#include <unordered_map>   // hash map     — tra cứu O(1): code→tên, AVP code→định nghĩa
#include <array>           // std::array   — mảng cố định an toàn (bọc T[N])
#include <set>             // tập hợp duy nhất

std::unordered_map<uint32_t, std::string> avp_names = {
    {1, "User-Name"}, {263, "Session-Id"}, {264, "Origin-Host"},
};
auto it = avp_names.find(263);
if (it != avp_names.end()) std::cout << it->second << "\n";   // "Session-Id"
ContainerCấu trúcDùng cho
vectormảng liềnbuffer gói tin, danh sách AVP
stringmảng charheader SIP, hostname, realm
unordered_maphash tabletra cứu nhanh code→tên
mapcây đỏ-đencần thứ tự theo key
array<T,N>mảng tĩnhheader kích thước cố định
settập duy nhấtdanh sách peer, IP whitelist

5.3. std::string & std::string_view

#include <string>
std::string realm = "epc.mnc001.mcc234.3gppnetwork.org";
realm.size();                     // độ dài
realm.substr(0, 3);               // "epc"
realm.find(".");                  // vị trí dấu chấm đầu

// string_view: "nhìn" vào chuỗi mà không copy (C++17) — hiệu năng cao khi parse
#include <string_view>
void check(std::string_view host);   // không copy, chỉ con trỏ+độ dài

Với protocol text như SIP, std::string/std::string_view an toàn hơn nhiều so với char* C. Với protocol nhị phân, dùng std::vector<uint8_t> (vì có thể chứa byte \0).


6. RAII — viên ngọc của C++ (diệt memory leak & UAF tận gốc)

RAII (Resource Acquisition Is Initialization): tài nguyên (bộ nhớ, file, socket) được giành lấy trong constructor và trả lại trong destructor. Khi object ra khỏi scope, destructor tự chạy → tài nguyên tự được giải phóng. Không thể quên free/close.

// So sánh:

// --- C: dễ quên/leak ---
void c_way() {
    uint8_t *buf = malloc(1024);
    if (something) return;        // LEAK! quên free
    free(buf);
}

// --- C++ RAII: không thể leak ---
void cpp_way() {
    std::vector<uint8_t> buf(1024);   // cấp phát trong constructor
    if (something) return;            // destructor của buf TỰ ĐỘNG free khi return
}                                     // tự free ở đây

RAII áp dụng cho mọi tài nguyên:

class FileGuard {
    FILE *f_;
public:
    explicit FileGuard(const char *path) : f_(fopen(path, "rb")) {}
    ~FileGuard() { if (f_) fclose(f_); }     // tự đóng file
    FILE *get() { return f_; }
};
// dùng: { FileGuard fg("x.pcap"); ... }  → file tự đóng dù return/exception ở đâu

Ý nghĩa an ninh khổng lồ: Phần lớn lỗ hổng memory (leak, UAF, double-free) trong C đến từ quản lý thủ công. RAII + smart pointer (module 06) xóa bỏ cả lớp lỗ hổng này ở mức ngôn ngữ. Khi audit, code C++ dùng RAII đúng cách an toàn hơn nhiều so với C tương đương — nhưng code C++ lai (trộn new/delete thủ công) lại nguy hiểm hơn vì phức tạp hơn.


7. Exception — xử lý lỗi (dùng có chừng mực trong code mạng)

#include <stdexcept>
void parse(const std::vector<uint8_t> &buf) {
    if (buf.size() < 20)
        throw std::runtime_error("packet quá ngắn");   // ném
    // ...
}

try {
    parse(packet);
} catch (const std::exception &e) {                     // bắt
    std::cerr << "lỗi parse: " << e.what() << "\n";
}

Lưu ý hiệu năng/độ tin cậy: trong đường dẫn xử lý gói tốc độ cao, nhiều dự án tránh exception (chi phí khi ném, khó kiểm soát luồng) và trả mã lỗi hoặc dùng std::optional/std::expected (C++23). Hãy theo phong cách của codebase bạn làm việc.


8. Ví dụ tổng hợp: lớp parser buffer an toàn (nền cho 08)

#include <cstdint>
#include <vector>
#include <stdexcept>
#include <span>          // C++20: "view" an toàn lên vùng nhớ (ptr+len)

// Đọc tuần tự, có kiểm tra biên — dùng cho mọi parser protocol
class ByteReader {
    const uint8_t *data_;
    size_t size_;
    size_t pos_ = 0;
public:
    ByteReader(const uint8_t *d, size_t n) : data_(d), size_(n) {}
    explicit ByteReader(std::span<const uint8_t> s) : data_(s.data()), size_(s.size()) {}

    size_t remaining() const { return size_ - pos_; }

    uint8_t u8() {
        if (remaining() < 1) throw std::runtime_error("hết dữ liệu");
        return data_[pos_++];
    }
    uint16_t u16be() {                       // đọc 16-bit big-endian
        if (remaining() < 2) throw std::runtime_error("hết dữ liệu");
        uint16_t v = (uint16_t(data_[pos_]) << 8) | data_[pos_+1];
        pos_ += 2; return v;
    }
    uint32_t u32be() {
        if (remaining() < 4) throw std::runtime_error("hết dữ liệu");
        uint32_t v = (uint32_t(data_[pos_])   << 24) | (uint32_t(data_[pos_+1]) << 16) |
                     (uint32_t(data_[pos_+2]) <<  8) |  uint32_t(data_[pos_+3]);
        pos_ += 4; return v;
    }
    void skip(size_t n) {
        if (remaining() < n) throw std::runtime_error("skip vượt biên");
        pos_ += n;
    }
};

Lớp ByteReader này gói gọn kiểm tra biên vào một chỗ — mọi u8/u16be/u32be đều an toàn. Đây chính là pattern khiến parser C++ khó bị overflow hơn parser C “tay không”. Ta sẽ dùng nó để parse Diameter/GTP ở 08.


9. Bài tập / Lab module 05

  1. Vector thay mảng: viết lại bài “đọc file vào buffer” (module 02) bằng std::vector<uint8_t> thay cho malloc/realloc. Chú ý: không còn free, không leak. Chạy ASan để xác nhận sạch.
  2. enum class command: định nghĩa enum class Cmd : uint32_t, viết hàm std::string name(Cmd). Thử so sánh Cmd::CER == 257 và xác nhận nó không biên dịch (an toàn kiểu).
  3. Map tra cứu AVP: dựng unordered_map<uint32_t,std::string> cho ~10 AVP code phổ biến (User-Name=1, Host-IP-Address=257, Auth-Application-Id=258, Session-Id=263, Origin-Host=264, Origin-Realm=296…). Viết hàm tra cứu trả “Unknown” nếu không thấy.
  4. Class DiameterMessage: mở rộng class ở mục 4 với method flags(), application_id() đọc từ raw_. Dùng const method đúng chỗ.
  5. RAII guard: viết SocketGuard RAII bọc một file descriptor (int), đóng close(fd) trong destructor. (Chuẩn bị cho module 07.)
  6. ByteReader: hoàn thiện ByteReader (mục 8), viết test: feed một buffer hợp lệ và một buffer cụt (thiếu byte), xác nhận buffer cụt ném exception thay vì đọc tràn. So sánh trải nghiệm với việc tự kiểm tra biên thủ công ở C — thấy rõ C++ giảm bug thế nào.

Cột mốc module 05: Bạn dùng std::vector/std::string/map thay cho mảng/chuỗi C thô, hiểu và áp dụng RAII để loại bỏ leak/UAF, dựng được class parser an toàn (ByteReader) đóng gói kiểm tra biên. Bạn đã có công cụ C++ đủ để viết tooling an ninh chắc chắn.

➡️ Tiếp theo: 06-Cpp-Nang-Cao.md — template, smart pointer, move semantics, C++ hiện đại.

Lộ trình học tập

Lập Trình C/C++

  1. 1 01 — C Cơ Bản
  2. 2 02 — Con Trỏ & Bộ Nhớ
  3. 3 03 — Struct, Union, Bitfield & Bố Cục Bộ Nhớ
  4. 4 04 — C Nâng Cao
  5. 5 05 — C++ Cốt Lõi
  6. 6 06 — C++ Hiện Đại
  7. 7 07 — Lập Trình Hệ Thống & Mạng
  8. 8 08 — Phân Tích Giao Thức Telecom Bằng C/C++
  9. 9 09 — An Ninh Bộ Nhớ & Khai Thác Cơ Bản
  10. 10 11 — Lập Trình Nhúng & Real-time (SDR-first)
  11. 11 Bài tập & Pet Project — Lộ trình C/C++ chuyên sâu
  12. 12 Tổng hợp các dạng printf để in giá trị trong C
  13. 13 Bitwise & Mask, Octet, Endian: Hướng dẫn parse protocol byte cho người mới
  14. 14 Tổng hợp: Con trỏ, kích thước kiểu dữ liệu và byte trong bộ nhớ
Contact
Facebook
TOP