Blog06 — C++ Hiện Đại
Bố cục bài viết
Trang này
Ngày tạo
05/08/2026
Cập nhật
Lượt xem
3
Đọc
10m
06 — C++ Hiện Đại
Mục tiêu: Hoàn thiện C++ với những công cụ làm nên sức mạnh và sự an toàn của ngôn ngữ: smart pointer (quản lý heap tự động — diệt UAF/leak), move semantics (truyền dữ liệu lớn không copy), template (code tổng quát), và các tính năng C17/20 (
optional,span,string_view, structured bindings). Đây là mức C để viết tooling phân tích/fuzzing hiện đại và đọc codebase C++ chất lượng cao.
1. Smart Pointer — quản lý heap không cần delete thủ công
new/delete thủ công trong C++ cũng nguy hiểm như malloc/free. Smart pointer áp dụng RAII cho con trỏ heap → tự giải phóng, không UAF, không leak, không double-free.
1.1. std::unique_ptr — sở hữu DUY NHẤT (mặc định nên dùng)
#include <memory>
std::unique_ptr<DiameterMessage> msg =
std::make_unique<DiameterMessage>(257, raw); // cấp phát, sở hữu
msg->command_code(); // dùng như con trỏ thường
// KHÔNG cần delete — tự hủy khi msg ra khỏi scope
// Không copy được (sở hữu duy nhất), nhưng MOVE được:
std::unique_ptr<DiameterMessage> other = std::move(msg); // chuyển quyền sở hữu
// giờ msg == nullptr, other nắm object
1.2. std::shared_ptr — sở hữu CHIA SẺ (đếm tham chiếu)
std::shared_ptr<AvpNode> node = std::make_shared<AvpNode>();
auto node2 = node; // copy được — bộ đếm tham chiếu = 2
// object hủy khi tham chiếu cuối cùng biến mất (đếm về 0)
Dùng
shared_ptrkhi nhiều nơi cùng sở hữu (vd. cây AVP có node được tham chiếu chéo). Chi phí cao hơnunique_ptr(đếm nguyên tử). Mặc định dùngunique_ptr; chỉshared_ptrkhi thật sự cần chia sẻ.
1.3. std::weak_ptr — tham chiếu KHÔNG sở hữu (phá vòng lặp)
std::weak_ptr<AvpNode> parent; // tránh chu trình shared_ptr → leak
| Smart pointer | Sở hữu | Copy? | Dùng cho |
|---|---|---|---|
unique_ptr | duy nhất | không (move được) | mặc định, 90% trường hợp |
shared_ptr | chia sẻ (đếm) | có | nhiều owner thật sự |
weak_ptr | không | có | phá chu trình tham chiếu |
Liên hệ an ninh: Use-after-free là lớp lỗ hổng số 1 trong phần mềm C/C++ hiện đại (trình duyệt, kernel, protocol stack).
unique_ptr/shared_ptrloại bỏ phần lớn UAF ở mức thiết kế. Khi audit C++, code dùng smart pointer nhất quán an toàn hơn hẳn codenew/deletetrần. Ngược lại, lạm dụng.get()rồi giữ con trỏ thô qua thời điểm object bị hủy lại tái tạo UAF — đây là chỗ cần soi khi review.
Quy tắc thép #8: Trong C++ hiện đại, gần như không bao giờ viết
new/deletetrần. Dùngmake_unique/make_shared. Container (vector…) lo phần còn lại.
2. Move Semantics — truyền dữ liệu lớn không sao chép
Khi trả về một vector<uint8_t> 1500 byte từ hàm, ta không muốn copy 1500 byte. Move semantics cho phép chuyển quyền sở hữu tài nguyên nội bộ (con trỏ + độ dài) thay vì sao chép nội dung.
std::vector<uint8_t> build_cer() {
std::vector<uint8_t> msg;
// ... điền 200 byte ...
return msg; // MOVE (không copy) — vector "trao" buffer nội bộ ra ngoài
}
std::vector<uint8_t> packet = build_cer(); // không có copy 200 byte
std::vector<uint8_t> a = {1,2,3};
std::vector<uint8_t> b = std::move(a); // chuyển buffer của a sang b
// giờ a rỗng (đã bị "móc ruột"), b = {1,2,3}
- lvalue = có tên, địa chỉ (
a). rvalue = tạm thời (build_cer(),1+2). std::move(x)= “ép xem x như rvalue, cho phép move-from”. (Bản thân nó không di chuyển gì — chỉ ép kiểu.)- Move constructor / move assignment:
T(T&&),T& operator=(T&&).
Vì sao quan trọng: packet processing hiệu năng cao xoay quanh việc tránh copy. Move semantics +
unique_ptrcho bạn zero-copy ownership transfer. Hiểu lvalue/rvalue cũng giúp đọc code template hiện đại.
Bẫy: sau
std::move(x),xở trạng thái “moved-from” (hợp lệ nhưng không xác định nội dung) — đừng dùng giá trị của nó nữa, chỉ gán lại hoặc hủy.
3. Template — code tổng quát theo kiểu
Template cho phép viết một hàm/class hoạt động với nhiều kiểu, biên dịch ra mã riêng cho từng kiểu (zero-cost abstraction).
3.1. Function template
template <typename T>
T read_be(const uint8_t *p); // đọc T (uint16_t/uint32_t/uint64_t) big-endian
template <>
uint16_t read_be<uint16_t>(const uint8_t *p) {
return (uint16_t(p[0]) << 8) | p[1];
}
template <>
uint32_t read_be<uint32_t>(const uint8_t *p) {
return (uint32_t(p[0]) << 24) | (uint32_t(p[1]) << 16) |
(uint32_t(p[2]) << 8) | uint32_t(p[3]);
}
uint32_t teid = read_be<uint32_t>(&buf[4]);
uint16_t length = read_be<uint16_t>(&buf[2]);
3.2. Class template
template <typename T, size_t N>
class FixedBuffer {
std::array<T, N> data_{};
public:
T &operator[](size_t i) { return data_.at(i); } // .at = kiểm tra biên
size_t size() const { return N; }
};
FixedBuffer<uint8_t, 1500> mtu_buffer;
Bạn không cần viết nhiều template lúc đầu, nhưng phải đọc được chúng — STL toàn template, và codebase C++ hiện đại dùng dày đặc. Hiểu
template<typename T>và cách STL container/algorithm tổng quát hóa là đủ để đọc trôi chảy.
4. STL Algorithms — xử lý dữ liệu khai báo (declarative)
<algorithm> + <numeric> cung cấp hàm thao tác trên dãy, thay vòng for thủ công (ít bug hơn).
#include <algorithm>
#include <numeric>
#include <vector>
std::vector<uint32_t> avp_codes = {264, 296, 257, 258, 263};
std::sort(avp_codes.begin(), avp_codes.end()); // sắp xếp
bool has = std::binary_search(avp_codes.begin(), avp_codes.end(), 263);
auto it = std::find(avp_codes.begin(), avp_codes.end(), 296); // tìm
size_t cnt = std::count_if(avp_codes.begin(), avp_codes.end(),
[](uint32_t c){ return c > 260; }); // đếm theo điều kiện
uint32_t total = std::accumulate(avp_codes.begin(), avp_codes.end(), 0u); // tổng
// Kiểm tra mọi phần tử thỏa điều kiện (vd: validate)
bool all_valid = std::all_of(avp_codes.begin(), avp_codes.end(),
[](uint32_t c){ return c != 0; });
Lambda — hàm vô danh tại chỗ (thường đi với algorithm)
auto is_grouped = [](uint32_t code) { return code == 297 || code == 284; };
int captured = 5;
auto f = [captured](int x) { return x + captured; }; // [bắt biến] (tham số) {thân}
auto g = [&captured](int x) { captured += x; }; // [&] bắt theo reference
5. Tính năng C++17/20 quan trọng cho parsing & an toàn
5.1. std::optional<T> — “có thể không có giá trị” (C++17)
#include <optional>
std::optional<uint32_t> find_avp(const std::vector<uint8_t> &msg, uint32_t code);
auto res = find_avp(msg, 263);
if (res) std::cout << *res << "\n"; // có giá trị
else std::cout << "không tìm thấy\n"; // nullopt
Thay cho mẹo “trả -1 nghĩa là lỗi” của C — rõ ràng và an toàn hơn cho hàm parser có thể thất bại.
5.2. std::span<T> — view (ptr + len) không sở hữu (C++20)
#include <span>
void parse(std::span<const uint8_t> data) { // nhận "cửa sổ" lên buffer, biết độ dài
if (data.size() < 8) return;
uint8_t first = data[0];
auto sub = data.subspan(8); // cửa sổ con từ byte 8 — không copy
}
std::vector<uint8_t> packet = ...;
parse(packet); // vector tự chuyển thành span
parse({raw_ptr, raw_len}); // hoặc từ con trỏ thô C
std::spanlà cách hiện đại để thay thế cặp(const uint8_t*, size_t)của C — vẫn zero-copy nhưng mang theo độ dài, giảm hẳn lỗi off-by-one/overflow. Dùngspancho mọi API parser mới.
5.3. std::string_view — view chuỗi không copy (C++17)
#include <string_view>
bool is_3gpp_realm(std::string_view host) {
return host.ends_with("3gppnetwork.org"); // C++20 ends_with
}
5.4. Structured bindings — giải nén gọn (C++17)
std::unordered_map<uint32_t, std::string> m = {{263, "Session-Id"}};
for (const auto &[code, name] : m) // giải nén key/value
std::cout << code << " = " << name << "\n";
auto [iter, inserted] = m.insert({264, "Origin-Host"});
5.5. constexpr — tính tại thời điểm biên dịch
constexpr uint32_t DIAMETER_HDR_LEN = 20; // hằng compile-time
constexpr uint32_t align4(uint32_t n) { return (n + 3u) & ~3u; } // chạy lúc biên dịch
6. Ví dụ tổng hợp: cây AVP an toàn bằng smart pointer + span
#include <cstdint>
#include <vector>
#include <memory>
#include <span>
#include <optional>
struct AvpNode {
uint32_t code = 0;
uint32_t flags = 0;
std::vector<uint8_t> value; // dữ liệu (tự quản lý)
std::vector<std::unique_ptr<AvpNode>> children; // grouped AVP — sở hữu con
};
// Trả unique_ptr: caller sở hữu cây; tự giải phóng, không leak/UAF
std::optional<std::unique_ptr<AvpNode>>
parse_avp(std::span<const uint8_t> data) {
if (data.size() < 8) return std::nullopt; // AVP header tối thiểu 8 byte
auto node = std::make_unique<AvpNode>();
node->code = (uint32_t(data[0])<<24)|(uint32_t(data[1])<<16)|
(uint32_t(data[2])<<8) | data[3];
node->flags = data[4];
uint32_t len = (uint32_t(data[5])<<16)|(uint32_t(data[6])<<8)|data[7]; // 24-bit
if (len < 8 || len > data.size()) return std::nullopt; // validate độ dài!
auto value = data.subspan(8, len - 8); // cửa sổ value, không copy
node->value.assign(value.begin(), value.end());
return node; // move ra ngoài
}
So với cây AVP bằng con trỏ thô ở module 03: phiên bản này tự quản lý bộ nhớ (smart pointer), biết độ dài (span), báo lỗi rõ ràng (optional), và validate length trước khi truy cập. Đây là chuẩn mực viết parser an toàn — kết hợp mọi thứ đã học.
7. Khi nào dùng C, khi nào C++ (cho công việc telecom security)
| Tình huống | Ngôn ngữ ưu tiên | Lý do |
|---|---|---|
| Đọc/audit Osmocom, FreeDiameter, Kamailio, kernel | C | Codebase là C thuần |
| Viết fuzz harness cho parser C | C/C++ | libFuzzer là C++ nhưng bọc hàm C |
| Tooling phân tích, decoder mới, CLI | C++ | RAII/STL an toàn & nhanh hơn để viết |
| Exploit PoC mức thấp, shellcode interop | C | Kiểm soát layout chính xác |
| srsRAN, một phần 5GC, công cụ lớn | C++ | Codebase là C++ |
Bạn cần đọc cả hai, viết cả hai. C để hiểu hệ thống và đọc stack lõi; C++ để viết tooling an toàn nhanh chóng.
8. Bài tập / Lab module 06
- unique_ptr factory: viết hàm
std::unique_ptr<DiameterMessage> make_cer(...)cấp phát và trả về. Chứng minh không cầndelete, chạy ASan xác nhận sạch. Thửstd::movenó sang biến khác. - Move vs copy: tạo
vector<uint8_t>lớn (1 MB), đo (bằng cảm nhận/log) việc trả về từ hàm là move chứ không copy. Thửstd::movevà in.size()của biến nguồn sau khi move (thấy nó rỗng). - Template read_be: hoàn thiện
read_be<uint16_t>vàread_be<uint32_t>(mục 3.1), test trên buffer thật, so vớintohs/ntohl. - STL algorithm: với danh sách AVP code, dùng
sort+binary_search+count_if(đếm grouped AVP) + lambda. Viết hoàn toàn bằng algorithm, không vòngforthủ công. - optional + span parser: hoàn thiện
parse_avp(mục 6). Test: (a) AVP hợp lệ → trả node đúng; (b) buffer 5 byte →nullopt; (c) length field = 0xFFFFFF (lớn hơn buffer) →nullopt(không crash). So sánh độ an toàn với parser C module 03. - Mini decoder: ghép
ByteReader(module 05) +parse_avpthành chương trình đọc một chuỗi AVP nối tiếp từ buffer và in cây (code, length, hex value). Đây là tiền thân của Diameter decoder ở08.
Cột mốc module 06 (và hết Phase 2): Bạn dùng thành thạo smart pointer (loại bỏ UAF/leak), move semantics (zero-copy), đọc/viết template, và dùng
optional/span/string_viewđể viết parser hiện đại an toàn. Bạn đủ C++ để viết tooling an ninh chất lượng production và đọc codebase C++ lớn.
➡️ Tiếp theo: 07-Lap-Trinh-He-Thong-Va-Mang.md — socket, SCTP, epoll: đưa code lên mạng thật.
Lộ trình học tập
Lập Trình C/C++
- 1 01 — C Cơ Bản
- 2 02 — Con Trỏ & Bộ Nhớ
- 3 03 — Struct, Union, Bitfield & Bố Cục Bộ Nhớ
- 4 04 — C Nâng Cao
- 5 05 — C++ Cốt Lõi
- 6 06 — C++ Hiện Đại
- 7 07 — Lập Trình Hệ Thống & Mạng
- 8 08 — Phân Tích Giao Thức Telecom Bằng C/C++
- 9 09 — An Ninh Bộ Nhớ & Khai Thác Cơ Bản
- 10 11 — Lập Trình Nhúng & Real-time (SDR-first)
- 11 Bài tập & Pet Project — Lộ trình C/C++ chuyên sâu
- 12 Tổng hợp các dạng printf để in giá trị trong C
- 13 Bitwise & Mask, Octet, Endian: Hướng dẫn parse protocol byte cho người mới
- 14 Tổng hợp: Con trỏ, kích thước kiểu dữ liệu và byte trong bộ nhớ