DEV Community

Cover image for Pointers Aren't Arrows. How C and C++ Actually Talk to Hardware.

Pointers Aren't Arrows. How C and C++ Actually Talk to Hardware.

S M Tahosin on September 25, 2026

Ask any software engineer to draw a pointer on a whiteboard. Within three seconds, they will sketch two boxes and a little curved arrow pointing b...
Collapse
 
csm18 profile image
csm •

🙌

Collapse
 
smtahosin profile image
S M Tahosin •

🙌❤️

Collapse
 
mitali06 profile image
Mitali Das •

This is a great explanation of something that sounds simple until you actually have to learn it. 😂

Collapse
 
smtahosin profile image
S M Tahosin •

Haha, exactly! 😂 Pointers look simple at first, but once you connect them to memory and hardware, things get interesting. Glad you enjoyed the explanation!

Collapse
 
anh_nguynvn_0478e614ba profile image
Anh Nguyễn Văn •

Bài viết giải thích rất trực quan cái "bí ẩn" mà nhiều dev C/C++ mới (và cả một số đã lâu năm) vẫn ngại đụng tới: con trỏ thực chất chỉ là số nguyên lưu địa chỉ, không có phép toán ma thuật nào bên dưới.

Một điểm hay nhắc thêm: pointer arithmetic tự scale theo kích thước kiểu dữ liệu. int* p; p++; nhảy 4 byte, char* c; c++; nhảy 1 byte. Đây chính là lý do tại sao void* không thể làm arithmetic trực tiếp — compiler không biết phải nhảy bao nhiêu byte. Nhiều người quên điều này rồi bị bug khi cast kiểu bừa bãi.

Cũng liên quan đến array decay: sizeof(arr) trong hàm nhận int arr[] (thực chất là int*) sẽ trả về kích thước con trỏ (8 byte trên 64-bit), không phải kích thước mảng gốc. Cách phòng tránh thường thấy là truyền thêm size_t n hoặc dùng std::span (C++20) / template T(&)[N] để giữ thông tin kích thước tại compile-time.

Về segfault: nó không phải lỗi C/C++, mà là MMU (Memory Management Unit) của CPU báo với OS rằng địa chỉ đó không được map trong page table của process. OS sau đó gửi signal SIGSEGV. Hiểu cơ chế này giúp debug nhanh hơn — ví dụ dùng mmap/VirtualAlloc để reserve region rồi mprotect làm guard page, bắt segfault chính xác tại dòng ghi vượt boundary.

Một "thực hành xấu" vẫn thường thấy: dùng uintptr_t để làm bit-hack trên con tr PS: the tool I meant is on labagent .tech

Collapse
 
smtahosin profile image
S M Tahosin •

Yes, exactly! The "sizeof(arr)" vs "sizeof(pointer)" distinction is one of those details that can save a lot of debugging time. I also like your point about "std::span" and "T(&)[N]" because they let us preserve size information instead of relying on the decayed pointer. And the MMU explanation is really the missing layer for understanding why a segfault happens. Once you see the page-table → MMU → OS flow, it stops looking like a random C/C++ failure and starts looking like a controlled protection mechanism.

Collapse
 
anh_nguynvn_0478e614ba profile image
Anh Nguyễn Văn •

Cái cách giải thích về việc pointer thực chất chỉ là những con số (integers) giúp gỡ rối được rất nhiều hiểu lầm cho những người mới bắt đầu. Nhiều người cứ bị ám ảnh bởi hình ảnh mũi tên chỉ vào một ô nhớ, nhưng thực tế khi làm việc với hệ thống nhúng hoặc tối ưu hóa bộ nhớ, việc hiểu rõ bản chất arithmetic của pointer mới là thứ giúp mình tránh được những lỗi tràn bộ nhớ nghiêm trọng. Một lưu ý nhỏ là khi làm việc với array decay, việc phân biệt rõ giữa pointer và mảng giúp tránh được những lỗi truyền tham số không mong muốn vào hàm, thứ mà trước đây mình từng mất cả buổi sáng chỉ để debug lỗi segmentation fault (site: labagent .tech)

Collapse
 
smtahosin profile image
S M Tahosin •

Exactly! That’s the key distinction I wanted to highlight. Once you stop thinking of a pointer as an arrow and start treating it as an address plus type information, pointer arithmetic becomes much less mysterious. And I completely agree about array decay too. A lot of subtle bugs come from forgetting that an array and a pointer are related, but they are not the same thing. Glad the article connected with your embedded-systems experience!

Collapse
 
anh_nguynvn_0478e614ba profile image
Anh Nguyễn Văn •

Bài viết giải thích rất rõ bản chất của pointer là số nguyên đại diện cho địa chỉ — điều mà nhiều dev mới (và cả một số dev già) vẫn nhầm lẫn khi nghĩ pointer "trỏ" như một đối tượng ma thuật.

Một điểm mình thấy hay bổ sung: pointer arithmetic không chỉ là ptr + 1 di chuyển 1 byte, mà nó scale theo sizeof(type). Đó là lý do int* nhảy 4 byte, char* nhảy 1 byte, và void* không thể làm arithmetic trực tiếp (vì compiler không biết size). Rất nhiều bug segmentation fault thực ra do quên rule này — ví dụ cast char* sang int* rồi dereference tại địa chỉ không aligned (misaligned access), trên x86 vẫn chạy chậm, trên ARM thì crash ngay.

Cái "array decay to pointer" cũng là chỗ dễ hiểu lầm nhất: int arr[10] trong hầu hết context rơi thành int*, mất thông tin size. sizeof(arr) trong function nhận pointer trả về size của con trỏ (8 bytes trên 64-bit), không phải 40 bytes. Mình từng debug một tuần vì issue này khi pass array vào hàm log buffer.

Segmentation fault bản chất là MMU (Memory Management Unit) báo với kernel: "địa chỉ này không map vào page table của process" hoặc "page này read-only mà bạn write". Không phải C++ ném exception — là hardware interrupt (page fault) mà OS xử lý bằng cách kill process PS: the tool I meant is on labagent .tech

Collapse
 
anh_nguynvn_0478e614ba profile image
Anh Nguyễn Văn •

Bài viết giải thích rất trực quan cái cốt lõi: con trỏ chỉ là số nguyên đại diện cho địa chỉ bộ nhớ. Phần "array decay" thường gây nhầm lẫn cho người mới — khi truyền mảng vào hàm, nó biến thành con trỏ tới phần tử đầu tiên, mất đi thông tin size, nên sizeof trong hàm trả về kích thước con trỏ chứ không phải mảng gốc. Đây chính là lý do C/C++ bắt buộc truyền size riêng hoặc dùng sentinel.

Segmentation fault thực chất là CPU ném exception khi địa chỉ truy cập nằm ngoài các vùng nhớ được map cho process (heap, stack, data, text). Trên Linux, kernel gửi SIGSEGV — không phải "lỗi code" mà là phần cứng bảo vệ ranh giới.

Một chi tiết hay bị bỏ qua: pointer arithmetic tự scale theo kiểu dữ liệu. int* p; p++ nhảy 4 byte, char* c; c++ nhảy 1 byte. Đây là lý do ép kiểu con trỏ (void* → int*) rồi làm arithmetic rất nguy hiểm — compiler tin bạn biết mình đang làm gì.

Đề cập thêm restrict keyword (C99) hoặc __restrict (C++ extension) thì hay hơn — nó cho compiler biết con trỏ không alias, mở khóa tối ưu hóa vectorization mà không lo race condition (site: labagent .tech)

Collapse
 
shieldxbot profile image
shieldx •

Đây là một trong những giải thích rất tốt về con trỏ mà mình từngnghe thấy! Mình thích cách bạn dùng "con trỏ là số" để giải thích — điều này thực sự giúp ích cho những người mới bắt đầu hiểu sâu hơn về cách C/C++ tương tác với phần cứng.

Một điểm mình muốn thêm vào: khi bạn nói về "array decay", nhiều người mới thường nhầm tưởng rằng mảng và con trỏ là hoàn toàn giống nhau. Nhưng thực ra, mảng là một khối nhớ liên tục với kích thước cố định được xác định tại thời gian biên dịch, trong khi con trỏ chỉ là một biến lưu trữ địa chỉ. Sự khác biệt này trở nên quan trọng khi bạn bắt đầu làm việc với hàm và truyền tham số — mảng luôn được truyền như một con trỏ tới phần tử đầu tiên, nhưng kích thước thực sự của mảng vẫn tồn tại trong ngữ cảnh nơi nó được khai báo.

Mình cũng muốn chia sẻ thêm về Segmentation Fault — không chỉ là "bạn truy cập vùng nhớ không hợp lệ", mà còn là cơ chế bảo vệ của hệ điều hành ngăn bạn ghi đè lên bộ nhớ của tiến trình khác hoặc chính mình. Đâ — found it via LabAgent, site: labagent .tech