Hàm nội tuyến

Trong khoa học máy tính, hàm nội tuyến (tiếng Anh: inline function) là một cấu trúc trong ngôn ngữ lập trình được sử dụng để đề nghị với chương trình biên dịch rằng một hàm cụ thể nào đó là đối tượng của việc khai triển nội tuyến (inline expansion); có nghĩa là, nó đề nghị rằng chương trình biên dịch nên chèn toàn bộ thân hàm vào trong từng ngữ cảnh, nơi hàm đó được sử dụng.

Động cơ
Khai triển nội tuyến thường được dùng để loại bỏ thời gian quá dụng (overhead) xảy ra khi gọi một hàm; kỹ thuật này thường được dùng cho các hàm thực thi thường xuyên, vì khi đó thời gian quá dụng chiếm phần đáng kể. Hàm nội tuyến còn có tác dụng giảm không gian bộ nhớ mà các hàm nhỏ chiếm chỗ, đồng thời cho phép các kỹ thuật tối ưu hoá (optimization) khác biến đổi mã.

Nếu không có hàm nội tuyến thì lập trình viên sẽ có rất ít hoặc không có quyền quyết định xem hàm nào được là hàm nội tuyến, hàm nào không; mà việc này sẽ hoàn toàn do trình biên dịch quyết định. Việc bổ sung khả năng điều khiển này cho phép lập trình viên khai thác các kiến thức đặc thù về chương trình ứng dụng, chẳng hạn những hàm thường xuyên được thực thi, để lựa chọn xem hàm nào cần là hàm nội tuyến. Tuy nhiên, nhiều trình biên dịch (chẳng hạn cho ngôn ngữ C++) giữ quyền quyết định cuối cùng về việc một hàm đã được lập trình đặt chế độ nội tuyến có thể thực sự được là một hàm nội tuyến hay không, quyết định này thường được dựa vào việc nội dung của hàm này có khả thi cho việc khai triển nội tuyến hay không.

Thêm nữa, trong một số ngôn ngữ, các hàm nội tuyến tương tác gần gũi với mô hình biên dịch (compilation model); chẳng hạn trong C++, một hàm nội tuyến phải được định nghĩa trong tất cả các môđun sử dụng hàm đó, trong khi đó chỉ cần định nghĩa các hàm thông thường trong một môđun mà thôi. Điều này cho phép biên dịch các môđun một cách độc lập với tất cả các môđun khác.

So sánh với macro
Theo truyền thống, trong các ngôn ngữ như C, việc khai triển nội tuyến được thực hiện ở mức mã nguồn bằng cách sử dụng các macro có tham số (parameterized macro). Việc sử dụng hàm nội tuyến mang lại một số lợi điểm so với cách tiếp cận truyền thống này:

Các lời gọi macro không thực hiện kiểm tra kiểu dữ liệu, và cũng không kiểm tra xem các tham đối (argument) có được định dạng đúng (well-formed) hay không, trong khi đó các lời gọi hàm thường thực hiện các kiểm tra này.
Do các macro của C chỉ thực hiện thay thế văn bản (textual substitution), điều này có thể dẫn tới các hiệu ứng phụ và sự không hiệu quả nằm ngoài dự tính, do việc đánh giá lại các tham đối và trật tự tính toán.
Lỗi biên dịch bên trong các macro thường rất khó hiểu, vì chúng là các lỗi trong phần mã đã được khai triển, chứ không phải phần mã do lập trình viên viết. (Lập trình viên có thể sẽ phải xem phần mã biên dịch sơ thảo của mã nguồn (pre-compile) để biết được lỗi xảy ra ở đâu.)
Có nhiều cấu trúc mà việc biểu diễn chúng bằng các macro là rất rắc rối hoặc không thể làm được, hoặc phải dùng đến những cú pháp hết sức khác biệt. Các hàm nội tuyến sử dụng cú pháp như các hàm thông thường, và có thể được chuyển thành nội tuyến hoặc bỏ chế độ nội tuyến tùy theo ý muốn một cách dễ dàng.
Thông tin tìm lỗi (debug) đối với mã nội tuyến thường hữu ích hơn thông tin dành cho mã macro đã được khai triển. Rất nhiều các bộ biên dịch còn khai triển nội tuyến một số các hàm đệ quy (recursive function) ; macro đệ quy thường là không hợp lệ.

Bjarne Stroustrup, người thiết kế C++, nhấn mạnh rằng nên tránh sử dụng macro mỗi khi có thể tránh được, và khuyến khích sử dụng rộng rãi các hàm nội tuyến.

Hỗ trợ của ngôn ngữ

C++, C99, và GNU C đều hỗ trợ hàm nội tuyến, song ANSI C (1990), phương ngữ phổ biến nhất của C trong thực tiễn, lại không hỗ trợ hàm nội tuyến. Trong ngôn ngữ lập trình ADA, hàm nội tuyến được sử dụng dưới hình thức của pragma. Đa số các ngôn ngữ khác, trong đó có Java và các ngôn ngữ lập trình hàm (functional languages), không cung cấp hàm nội tuyến nhưng lại thực hiện khai triển nội tuyến rất mạnh. Các trình biên dịch khác nhau có thể khác nhau ở độ phức tạp của các hàm mà chúng có khả năng khai triển nội tuyến. Các bộ biên dịch C++ chính thống như Microsoft Visual C++ và GCC (GNU Compiler Collection) có hỗ trợ một tùy chọn cho phép bộ biên dịch tự động biến mọi hàm có thể thành hàm nội tuyến, ngay cả khi các hàm này không được đánh dấu là hàm nội tuyến.

Một hàm nội tuyến có thể được viết trong C++ như sau:
inline int max (int a, int b)
{
if (a > b)
return a;
else
return b;
}

a = max (x, y); // hiện tương đương với "a = (x > y ? x: y);"

Những vấn đề với các hàm nội tuyến

Bên cạnh những vấn đề liên quan đến khai triển nội tuyến nói chung, với vai trò một tính năng của ngôn ngữ, hàm nội tuyến có thể không đáng giá như người ta có thể hình dung, vì một số lý do sau:
Thông thường, để quyết định xem một hàm cụ thể nào đó có nên là hàm nội tuyến hay không, trình biên dịch có vị trí tốt hơn con người; cụ thể, trình biên dịch có thể không muốn hoặc không có khả năng nội tuyến hóa nhiều hàm mà con người muốn nó làm.
Trong quá trình phát triển của các hàm, chúng có thể trở nên thích hợp làm hàm nội tuyến, hoặc trở nên không còn thích hợp như đã từng. Mặc dù đánh dấu một hàm là nội tuyến hay không còn nội tuyến là một việc dễ dàng hơn việc chuyển đổi các hàm sang macro và ngược lại, công việc này vẫn đòi hỏi thêm công sức bảo trì, mà trong phần lớn các trường hợp, việc làm này đem lại lợi ích tương đối nhỏ.
Đối với các chương trình biên dịch đơn giản viết bằng C, việc sử dụng quá nhiều hàm nội tuyến có thể làm tăng thời gian biên dịch, do phần thân của các hàm đó phải được bổ sung dưới dạng văn bản vào bất cứ tập mã nguồn nào sử dụng chúng. (có thể thấy khi xem mã nguồn sau khi biên dịch sơ thảo (pre-compiled source) mà bộ biên dịch tạo ra).

Về các vấn đề với chính bản thân quá trình tối ưu hóa, thay vì về tính năng của ngôn ngữ, xem các vấn đề trong khai triển nội tuyến.

Trích dẫn

:"Việc khai báo một hàm [... ] dùng chỉ định inline thông báo rằng hàm này là một hàm nội tuyến. Chỉ định inline báo với việc thực hiện chương trình rằng việc thay thế nội tuyến của phần thân hàm tại thời điểm hàm được khởi động là một lựa chọn được cân nhắc hơn là cơ chế thi hành hàm bình thường. Việc thực hiện một cơ chế để thay thế hàm nội tuyến tại thời điểm hàm được khởi động là một việc không cần thiết; song, ngay cả khi bỏ qua việc thay thế nội tuyến, các nguyên tắc khác đối với các hàm nội tuyến được định nghĩa trong phần 7.1.2 cũng vẫn phải được tôn trọng."
:&mdash; ISO 14882:1998(E), the current C++ standard, section 7.1.2

:"Một hàm được khai báo với chỉ định hàm inline là một hàm nội tuyến. [... ] Việc nội tuyến hóa một hàm gợi ý rằng các yêu cầu khởi động hàm phải được thi hành càng nhanh càng tốt. Phạm vi ảnh hưởng của những gợi ý như vậy có hiệu quả bao nhiêu còn tùy thuộc vào thực hiện (implementation-defined) (ghi chú: Lấy ví dụ, một thực hiện có thể sẽ không bao giờ thi hành thay thế nội tuyến, hoặc chỉ thi hành thay thế nội tuyến đối với các yêu cầu khởi động trong phạm vi (scope) của một khai báo nội tuyến mà thôi)

:"[... ] Một khai báo nội tuyến không cung cấp định nghĩa ngoại vi (external definition) cho một hàm, cũng không ngăn cấm một định nghĩa ngoại vi trong một đơn vị thông dịch (translation unit) khác. Định nghĩa nội tuyến cung cấp một hình thức thay thế cho định nghĩa ngoại vi, và một bộ thông dịch (translator) có thể sử dụng để thực hiện bất cứ một yêu cầu khởi động hàm nào trong cùng đơn vị thông dịch. Việc một yêu cầu thi hành một hàm nào đó sử dụng định nghĩa nội tuyến hoặc sử dụng định nghĩa ngoại tuyến là một việc không được xác định cụ thể."
:&mdash; ISO 9899:1999(E), the C99 standard, section 6.7.4

Xem thêm
Khai triển nội tuyến (Inline expansion)

Tham khảo

Liên kết ngoài
Inline functions with the GNU Compiler Collection (GCC)