C/C++

Bài viết này của tác giả Nguyễn Thái Dương.

Những ai từng học lập trình hướng đối tượng OOP chắc chắn đều biết đến khái niệm nạp chồng (Override). Nhưng thông thường, ít bạn quan tâm đến việc compiler đã xử lý nó như thế nào. Phía sau hậu trường hệ thống đã làm gì để tạo nên điều kì diệu? Bài viết này hi vọng sẽ giúp các bạn có câu trả lời xác đáng nhất.

Để dễ dàng trong việc hiển thị các giá trị trong vùng bộ nhớ, tôi chọn C++. Đối với Java hay các ngôn ngữ lập trình khác, tôi tin rằng các bạn cũng có thể dễ dàng luận ra được dựa trên cơ chế của C++

Trước hết,  chúng ta cần tìm hiểu về hàm virtual. Hãy xét 1 class đơn giản sau (Giả sử ta compile cho hệ vi xử lý 64 bit). Bạn có thể copy paste vào http://cpp.sh để chạy thử:

Kết quả sẽ như sau:

size of Sample1: 16
This is virtual method 1
This is virtual method 2

Yeah!!. Đến đây bạn thấy không? Chúng ta có thể call được class method mà không cần gọi thông qua instance của nó. Ta hãy thử đi phân tích cấu trúc dữ liệu 16 byte của class Sample1:

#vị trí (byte)size (bytes)field
108con trỏ trỏ tới virtual method table (VMT)
284member1
3124member2

Chỗ này có 1 khái niệm mới là Virtual Method Table (VMT). Vậy nó là cái gì? Virtual Method Table thực chất là một mảng các con trỏ hàm chứa địa chỉ của các hàm virtual trong class. Trong ví dụ trên sẽ là:

#Vị trí (byte)Size (bytes)Field
108địa chỉ của vMethod1
288địa chỉ của vMethod2

OK, có 1 điều hơi lấn cấn ở đây khi bạn sử dụng member1 trong vMethod1:

Kết quả sẽ có dạng như sau:

size of Sample1: 16
This is virtual method 1
member1 = 1268843168
This is virtual method 2

giá trị member1 là 1 giá trị không phải là 1000 như mình đã gán ở trên mà là 1 giá trị rác. Nguyên nhân là do lời gọi virtualMethodTable[0](); chỉ đơn thuần là gọi 1 đoạn code của hàm mà chưa truyền con trỏ this vào trong hàm đó (vMethod1).

Thông thường, lời gọi đúng phải là: a->vMethod1();

Giờ chúng ta sẽ tìm cách truyền con trỏ a vào cho lời gọi virtualMethodTable[0]();. Giờ ta sửa lại 1 chút:

Giờ chạy thử nhé:

size of Sample1: 16
This is virtual method 1
member1 = 1000
This is virtual method 2

Yeah. Giờ member1 đã là 1000. Đúng với giá trị chúng ta truyền vào. Giờ chúng ta thử fake lại cách 1 instance được tạo ra từ 1 class theo cách không dùng class nhé:

Kết quả:

size of Sample1: 16
This is virtual method 1
member1 = 1000
This is virtual method 2 ————–FAKE CLASS—————-
fake virtual method1 – member1: 2000
fake virtual method2 – member2: 3000

Đến đây chúng ta ít nhiều đã hình dung ra được cách C++ tổ chức dữ liệu trong một class như thế nào. Giờ chúng ta sẽ cùng tìm hiểu xem cách mà C++ override một method trong Class như thế nào:

Kết quả:

Sample1 – vMethod1 Addr: 0x400980
Sample1 – vMethod2 Addr: 0x400950 ——————————–
Sample2 – vMethod1 Addr: 0x400970
Sample2 – vMethod2 Addr: 0x400950

Chúng ta thấy ngay, Class B được override method1 nên địa chỉ của vMethod1 trong VMT của b khác với của a, trong khi vMethod2 thì giống hết nhau vì không bị override.

Ta có thể fake lại việc override một cách đơn giản như sau:

Kết quả:

size of Sample1: 16
This is virtual method 1 member1 = 1000
This is virtual method 2

————–FAKE CLASS—————-

fake virtual method1 – member1: 2000
fake virtual method2 – member2: 3000

————–FAKE INHERITANCE CLASS—————-

override fake virtual method1 – member1: 2000
fake virtual method2 – member2: 3000

Yeah. Ta thấy ngay khi gọi b->vMethod1() thì hàm override_fakeVMethod1 được gọi. Vậy là chúng ta đã thực hiện thành công việc override hàm method.

Bài viết này hi vọng mang đến cho các bạn 1 cái nhìn rõ hơn về khía cạnh cài đặt của virtual method và override – cái mà trình biên dịch đã che dấu khỏi developer. Chúng ta sẽ cùng nhau tìm hiểu những điều thú vị khác nằm sâu bên trong chương trình để hiểu rõ hơn cách thức mà máy tính hoạt động đằng sau những dòng code của bạn.

Xin cảm ơn và hẹn gặp lại

Nguyễn Thái Dương.

User Avatar

Code Toàn Bug

Code nhiều bug nhiều!

Leave a Reply

Your email address will not be published. Required fields are marked *