Pass by value and pass by reference java là gì năm 2024

🌱 Pass by Value v/s Pass by Reference

Ở bài viết trước mình đã giới thiệu về quá trình Function Call, việc định nghĩa và sử dụng Function là một việc rất quen thuộc khi lập trình, đặc biệt là trong C/C++, khi sử dụng function, chúng ta cần quan tâm nhiều hơn về mặt thời gian và bộ nhớ.

C cung cấp cho người dùng 2 cách sử dụng call function, tương ứng với 2 kiểu tham số đầu vào, đó là call by Value và call by Reference (Trong C++ cách này sẽ là Pass by Pointer, sẽ được giới thiệu ở bài viết sau).

Một ví dụ điển hình để so sánh 2 kiểu function call trên bài toán "Swap two integer - Đảo giá trị của 2 số nguyên". Bài toán này thì các bạn học C đã khá quen thuộc và biết cách viết đúng rồi, nhưng ở đây mình vẫn muốn bắt đầu với một cách viết sai:

Pass by value and pass by reference java là gì năm 2024

👉 Pass by Value - Tham trị

Hiểu đơn giản đó là cách chúng ta truyền trực tiếp giá trị của các tham số vào hàm.

Như đã giới thiệu ở bài trước, khi call function, giá trị của các Argument sẽ được đẩy lên Stack, và các thao tác với các Argument này sẽ chính là thao tác đến các ô nhớ tạm trên Stack. Ví dụ, call function trên:

int a = 4, b = 5; swap(a, b);

💬 Trước khi call function, dễ thấy hai biến a và b có thể đặt trên Stack hoặc Data.

Pass by value and pass by reference java là gì năm 2024

💬 Khi call function, không nhắc đến các Register, address, các Argument sẽ được đẩy lên Stack, và chúng ta sẽ có phiên bản thứ 2 của các biến a, b, đó là a_copy và b_copy, biến local temp sẽ được gán giá trị của biến a_copy (temp = 4).

Pass by value and pass by reference java là gì năm 2024

💬 Sau khi thực hiện các phép toán trong hàm, trước khi thoát khỏi hàm, các giá trị a_copy và b_copy đã bị hoán đổi so với giá trị lúc đầu. Tuy nhiên có thể thấy ví trị của biến a và b trên bộ nhớ thì không hề bị thay đổi. Và sau khi thoát khỏi hàm thì a_copy và b_copy sẽ bị unstacking, trong khi 2 biến a và b thì vẫn không bị tác động từ đầu đến cuối.

Pass by value and pass by reference java là gì năm 2024

👉 Pass by Reference - Tham chiếu

Đối với bài toán trên thì cách làm đúng và đơn giản nhất đó là sử dụng Pass by Reference:

Pass by value and pass by reference java là gì năm 2024

Cách sử dụng pass by Value ở trên tác động vào một phiên bản copy của biến nên không thể tác động làm thay đổi giá trị của biến. Vì vậy, cách sử dụng pass by Reference sẽ khắc phục được vấn đề trên, ý tưởng là truyền địa chỉ của biến vào trong hàm nhằm tác động trực tiếp đến vị trí của biến trên bộ nhớ.

➤ Ví dụ call funtion trên

int a = 4, b = 5; swap(&a, &b);

💬 Khi call function, không nhắc đến các Register, address, các Argument sẽ được đẩy lên Stack, trong trường hợp này là địa chỉ của các biến a và b, giả sử là 2 địa chỉ 0x100 và 0x104 như hình. Biến temp sẽ được gán giá trị hiện tại của biến a bằng phép gán: temp = *addr_a = *0x100 = 4.

Pass by value and pass by reference java là gì năm 2024

💬 Sau khi thực hiện các phép toán trong hàm, trước khi thoát khỏi hàm, giá trị của biến a và b sẽ bị thay đổi, bởi vì các phép toán của chúng ta đã tác động trực tiếp vào 2 vị trí của 2 biến này thông qua địa chỉ. Kết thúc hàm thíc các giá trị biến tạm bị unstacking trong khi giá trị của a và b đã bị thay đổi.

Pass by value and pass by reference java là gì năm 2024

👉 Trường hợp sử dụng

💬 Dễ thấy, theo những phân tích ở trên, Pass by Value được sử dụng khi chúng ta không muốn tác động vào giá trị của biến, còn Pass by Reference được sử dụng khi muốn tác động thay đổi giá trị của biến sau khi gọi hàm.

Pass by value and pass by reference java là gì năm 2024

💬 Trường hợp sử dụng thứ 2 được rút ra từ đặc điểm của Function Call, việc gọi hàm ảnh hưởng khá lớn đến bộ nhớ Stack, đặc biệt khi kích thước tham số truyền vào là lớn (Ví dụ truyền các Struct với kích thước lớn hơn 8 bytes).

  • Nếu sử dụng Pass by Value, khi call function cần tạo ra một biến tạm với kích thước bằng với kích thước của biến truyền vào. Và nếu biến có kích thước lớn như nói ở trên thì sẽ tốn rất nhiều bộ nhớ Stack, dễ dẫn đến Stack Overflow. Để giải quyết vấn đề trên, có thể sử dụng Pass by Reference, khi call function chỉ cần tạo một biến tạm với kích thước bằng kích thước của con trỏ để lưu địa chỉ của biến truyền vào (Kích thước của con trỏ chỉ là 4 hoặc 8 byte, phụ thuộc vào kiến trúc của CPU). Cách này sẽ làm giảm "gánh nặng" cho bộ nhớ Stack khi call function 😅