Không có lợi thế nào tôi có thể nghĩ đến (nhưng xem ghi chú cho JasonS ở phía dưới), gói một dòng mã dưới dạng hàm hoặc chương trình con. Ngoại trừ có lẽ bạn có thể đặt tên cho chức năng là "có thể đọc được". Nhưng bạn cũng có thể bình luận dòng. Và vì việc gói một dòng mã trong một hàm tốn bộ nhớ mã, không gian ngăn xếp và thời gian thực hiện đối với tôi, nó dường như chủ yếu là phản tác dụng. Trong một tình huống giảng dạy? Nó có thể có ý nghĩa. Nhưng điều đó phụ thuộc vào lớp học sinh, sự chuẩn bị trước của họ, chương trình giảng dạy và giáo viên. Hầu hết, tôi nghĩ đó không phải là một ý tưởng tốt. Nhưng đó là ý kiến của tôi.
Điều này đưa chúng ta đến điểm mấu chốt. Khu vực câu hỏi rộng của bạn, trong nhiều thập kỷ, là một vấn đề tranh luận và cho đến ngày nay vẫn là một vấn đề tranh luận. Vì vậy, ít nhất là khi tôi đọc câu hỏi của bạn, dường như tôi là một câu hỏi dựa trên ý kiến (như bạn đã hỏi nó).
Nó có thể được chuyển khỏi vị trí dựa trên ý kiến, nếu bạn phải chi tiết hơn về tình huống và mô tả cẩn thận các mục tiêu bạn giữ là chính. Bạn càng xác định tốt các công cụ đo lường của mình, câu trả lời càng khách quan.
Nói rộng ra, bạn muốn làm như sau cho bất kỳ mã hóa nào . .
- Hãy nhất quán về cách tiếp cận của bạn, để người khác đọc mã của bạn có thể phát triển sự hiểu biết về cách bạn tiếp cận quá trình mã hóa của mình. Không nhất quán có lẽ là tội ác tồi tệ nhất có thể. Nó không chỉ gây khó khăn cho người khác mà còn gây khó khăn cho chính bạn khi quay trở lại mã năm sau đó.
- Ở mức độ có thể, hãy thử và sắp xếp mọi thứ sao cho việc khởi tạo các phần chức năng khác nhau có thể được thực hiện mà không liên quan đến việc đặt hàng. Trong trường hợp yêu cầu đặt hàng, nếu đó là do sự kết hợp chặt chẽ của hai chức năng con có liên quan cao, thì hãy xem xét một khởi tạo duy nhất cho cả hai để có thể sắp xếp lại mà không gây hại. Nếu điều đó là không thể, thì hãy ghi lại yêu cầu đặt hàng khởi tạo.
- Đóng gói kiến thức ở chính xác một nơi, nếu có thể. Các hằng số không được trùng lặp khắp nơi trong mã. Các phương trình giải quyết một số biến nên tồn tại ở một và chỉ một nơi. Và như thế. Nếu bạn thấy mình sao chép và dán một số dòng thực hiện một số hành vi cần thiết ở nhiều địa điểm khác nhau, hãy xem xét một cách để nắm bắt kiến thức đó ở một nơi và sử dụng nó khi cần thiết. Ví dụ, nếu bạn có một cấu trúc cây mà phải đi theo một cách cụ thể, làm khôngsao chép mã đi bộ cây ở mỗi và mọi nơi bạn cần lặp qua các nút cây. Thay vào đó, hãy chụp phương pháp đi bộ trên cây ở một nơi và sử dụng nó. Theo cách này, nếu cây thay đổi và phương thức đi bộ thay đổi, bạn chỉ có một nơi để lo lắng và tất cả phần còn lại của mã "chỉ hoạt động đúng".
- Nếu bạn trải đều tất cả các thói quen của mình lên một tờ giấy lớn, phẳng, với các mũi tên nối với chúng khi chúng được gọi bởi các thói quen khác, bạn sẽ thấy trong bất kỳ ứng dụng nào sẽ có "cụm" các thói quen có rất nhiều mũi tên giữa họ nhưng chỉ có một vài mũi tên bên ngoài nhóm. Vì vậy, sẽ có ranh giới tự nhiên của các thói quen kết hợp chặt chẽ và kết nối lỏng lẻo giữa các nhóm khác của các thói quen kết hợp chặt chẽ. Sử dụng thực tế này để tổ chức mã của bạn thành các mô-đun. Điều này sẽ hạn chế sự phức tạp rõ ràng của mã của bạn, về cơ bản.
Trên đây chỉ là nói chung về tất cả các mã. Tôi đã không thảo luận về việc sử dụng các tham số, biến cục bộ hoặc biến toàn cục, v.v ... Lý do là vì lập trình nhúng, không gian ứng dụng thường đặt các ràng buộc mới cực kỳ và rất quan trọng và không thể thảo luận về tất cả chúng mà không thảo luận về mọi ứng dụng nhúng. Và điều đó không xảy ra ở đây, dù sao đi nữa.
Những ràng buộc này có thể là bất kỳ (và hơn thế nữa) trong số này:
- Hạn chế chi phí nghiêm trọng đòi hỏi MCU cực kỳ nguyên thủy với RAM cực nhỏ và hầu như không có số lượng pin I / O. Đối với những điều này, bộ quy tắc hoàn toàn mới áp dụng. Ví dụ: bạn có thể phải viết mã lắp ráp vì không có nhiều không gian mã. Bạn có thể phải sử dụng CHỈ các biến tĩnh vì việc sử dụng các biến cục bộ quá tốn kém và mất thời gian. Bạn có thể phải tránh sử dụng quá nhiều chương trình con vì (ví dụ: một số phần PIC của Microchip) chỉ có 4 thanh ghi phần cứng để lưu địa chỉ trả về chương trình con. Vì vậy, bạn có thể phải "làm phẳng" mã của mình. Vân vân.
- Các hạn chế về năng lượng nghiêm trọng đòi hỏi mã được chế tạo cẩn thận để khởi động và tắt hầu hết MCU và đặt các giới hạn nghiêm trọng về thời gian thực thi mã khi chạy ở tốc độ tối đa. Một lần nữa, điều này có thể yêu cầu một số mã hóa lắp ráp, đôi khi.
- Yêu cầu thời gian nghiêm trọng. Ví dụ, có những lúc tôi phải đảm bảo rằng việc truyền cống mở 0 phải thực hiện chính xác số chu kỳ tương tự như việc truyền 1. Và việc lấy mẫu này cũng phải được thực hiện với một giai đoạn tương đối chính xác đến thời điểm này. Điều này có nghĩa là C KHÔNG thể được sử dụng ở đây. Cách duy nhất có thể để thực hiện đảm bảo đó là cẩn thận làm mã lắp ráp. (Và thậm chí sau đó, không phải lúc nào cũng trên tất cả các thiết kế ALU.)
Và như thế. (Mã dây cho thiết bị y tế quan trọng trong cuộc sống cũng có cả một thế giới riêng.)
Kết quả cuối cùng ở đây là mã hóa nhúng thường không phải là một số miễn phí, nơi bạn có thể mã hóa như bạn có thể trên máy trạm. Thường có những lý do nghiêm trọng, cạnh tranh cho một loạt các ràng buộc rất khó khăn. Và những điều này có thể tranh luận mạnh mẽ chống lại các câu trả lời truyền thống và chứng khoán hơn .
Về khả năng đọc, tôi thấy rằng mã có thể đọc được nếu nó được viết theo kiểu nhất quán mà tôi có thể học khi đọc nó. Và trong trường hợp không có sự cố tình làm xáo trộn mã. Thực sự không có nhiều yêu cầu hơn.
Mã có thể đọc được có thể khá hiệu quả và nó có thể đáp ứng tất cả các yêu cầu trên mà tôi đã đề cập. Điều chính là bạn hiểu đầy đủ những gì mỗi dòng mã bạn viết tạo ra ở cấp độ lắp ráp hoặc máy, khi bạn mã hóa nó. C ++ đặt một gánh nặng nghiêm trọng lên lập trình viên ở đây vì có nhiều tình huống trong đó các đoạn mã C ++ giống hệt nhau thực sự tạo ra các đoạn mã máy khác nhau có hiệu suất rất khác nhau. Nhưng C, nói chung, chủ yếu là một ngôn ngữ "những gì bạn thấy là những gì bạn nhận được". Vì vậy, nó an toàn hơn trong vấn đề đó.
EDIT mỗi JasonS:
Tôi đã sử dụng C từ năm 1978 và C ++ từ khoảng năm 1987 và tôi đã có nhiều kinh nghiệm sử dụng cả cho cả máy tính lớn, máy tính mini và (hầu hết) các ứng dụng nhúng.
Jason đưa ra một nhận xét về việc sử dụng 'nội tuyến' làm công cụ sửa đổi. (Theo quan điểm của tôi, đây là một khả năng tương đối "mới" vì đơn giản là nó không tồn tại được một nửa cuộc đời tôi hoặc hơn khi sử dụng C và C ++.) Việc sử dụng các hàm nội tuyến thực sự có thể thực hiện các cuộc gọi như vậy (ngay cả đối với một dòng mã) khá thực tế. Và nó tốt hơn nhiều, nếu có thể, hơn là sử dụng macro vì cách gõ mà trình biên dịch có thể áp dụng.
Nhưng cũng có những hạn chế. Đầu tiên là bạn không thể dựa vào trình biên dịch để "đưa ra gợi ý". Hên xui. Và có những lý do tốt để không đưa ra gợi ý. (Đối với một ví dụ rõ ràng, nếu địa chỉ của chức năng được thực hiện, điều này yêu cầu khởi tạo chức năng và sử dụng địa chỉ để thực hiện cuộc gọi sẽ ... yêu cầu một cuộc gọi. Sau đó, mã không thể được nội tuyến.) những lý do khác, là tốt. Trình biên dịch có thể có nhiều tiêu chí khác nhau để họ đánh giá cách xử lý gợi ý. Và là một lập trình viên, điều này có nghĩa là bạn phảidành thời gian tìm hiểu về khía cạnh đó của trình biên dịch nếu không bạn có thể đưa ra quyết định dựa trên những ý tưởng thiếu sót. Vì vậy, nó cũng tạo thêm gánh nặng cho người viết mã cũng như bất kỳ người đọc nào và bất kỳ ai dự định chuyển mã sang một số trình biên dịch khác.
Ngoài ra, trình biên dịch C và C ++ hỗ trợ biên dịch riêng. Điều này có nghĩa là họ có thể biên dịch một đoạn mã C hoặc C ++ mà không cần biên dịch bất kỳ mã nào khác có liên quan cho dự án. Để mã nội tuyến, giả sử trình biên dịch có thể chọn làm như vậy, nó không chỉ phải có khai báo "trong phạm vi" mà còn phải có định nghĩa. Thông thường, các lập trình viên sẽ làm việc để đảm bảo rằng đây là trường hợp nếu họ đang sử dụng 'nội tuyến'. Nhưng nó dễ dàng cho những sai lầm leo vào.
Nói chung, trong khi tôi cũng sử dụng nội tuyến nơi tôi nghĩ nó phù hợp, tôi có xu hướng cho rằng tôi không thể dựa vào nó. Nếu hiệu suất là một yêu cầu quan trọng và tôi nghĩ OP đã viết rõ ràng rằng đã có một hiệu suất đáng kể khi họ đi đến một tuyến đường "chức năng" hơn, thì tôi chắc chắn sẽ chọn tránh dựa vào nội tuyến như một cách thực hành mã hóa và thay vào đó sẽ đi theo một mô hình viết mã hơi khác, nhưng hoàn toàn nhất quán.
Một lưu ý cuối cùng về 'nội tuyến' và các định nghĩa là "trong phạm vi" cho một bước biên dịch riêng biệt. Có thể (không phải lúc nào cũng đáng tin cậy) cho công việc được thực hiện ở giai đoạn liên kết. Điều này có thể xảy ra khi và chỉ khi trình biên dịch C / C ++ chôn đủ chi tiết vào các tệp đối tượng để cho phép trình liên kết hành động theo yêu cầu 'nội tuyến'. Cá nhân tôi chưa có kinh nghiệm về một hệ thống liên kết (bên ngoài Microsoft) hỗ trợ khả năng này. Nhưng nó có thể xảy ra. Một lần nữa, việc có nên dựa vào hay không sẽ phụ thuộc vào hoàn cảnh. Nhưng tôi thường cho rằng điều này đã không được đưa vào trình liên kết, trừ khi tôi biết cách khác dựa trên bằng chứng tốt. Và nếu tôi dựa vào nó, nó sẽ được ghi nhận ở một nơi nổi bật.
C ++
Đối với những người quan tâm, đây là một ví dụ về lý do tại sao tôi vẫn khá thận trọng với C ++ khi mã hóa các ứng dụng nhúng, mặc dù nó đã sẵn sàng ngày hôm nay. Tôi sẽ đưa ra một số thuật ngữ mà tôi nghĩ rằng tất cả các lập trình viên C ++ nhúng cần biết lạnh lùng :
- chuyên môn hóa một phần mẫu
- vtables
- đối tượng cơ sở ảo
- khung kích hoạt
- kích hoạt khung thư giãn
- sử dụng con trỏ thông minh trong các nhà xây dựng, và tại sao
- tối ưu hóa giá trị
Đó chỉ là một danh sách ngắn. Nếu bạn chưa biết tất cả mọi thứ về các điều khoản đó và tại sao tôi liệt kê chúng (và nhiều điều nữa tôi không liệt kê ở đây) thì tôi khuyên bạn không nên sử dụng C ++ cho công việc nhúng, trừ khi đó không phải là một lựa chọn cho dự án .
Chúng ta hãy xem nhanh ngữ nghĩa ngoại lệ của C ++ để có được một hương vị.
MộtB
Một
.
.
foo ();
String s;
foo ();
.
.
Một
B
Trình biên dịch C ++ nhìn thấy cuộc gọi đầu tiên đến foo () và chỉ có thể cho phép một khung kích hoạt bình thường thư giãn xảy ra, nếu foo () ném ngoại lệ. Nói cách khác, trình biên dịch C ++ biết rằng không cần thêm mã vào thời điểm này để hỗ trợ quá trình thư giãn khung liên quan đến xử lý ngoại lệ.
Nhưng một khi String s đã được tạo, trình biên dịch C ++ biết rằng nó phải được hủy đúng trước khi cho phép mở khung hình, nếu một ngoại lệ xảy ra sau đó. Vì vậy, cuộc gọi thứ hai đến foo () khác về mặt ngữ nghĩa so với cuộc gọi đầu tiên. Nếu lệnh gọi thứ 2 đến foo () đưa ra một ngoại lệ (điều này có thể hoặc không thể thực hiện), trình biên dịch phải đặt mã được thiết kế để xử lý việc phá hủy Chuỗi s trước khi xảy ra tình trạng bung khung thông thường. Điều này khác với mã cần thiết cho cuộc gọi đầu tiên đến foo ().
(Có thể thêm các trang trí bổ sung trong C ++ để giúp hạn chế vấn đề này. Nhưng thực tế là, các lập trình viên sử dụng C ++ chỉ đơn giản là phải nhận thức rõ hơn về ý nghĩa của từng dòng mã họ viết.)
Không giống như malloc của C, mới của C ++ sử dụng các ngoại lệ để báo hiệu khi không thể thực hiện cấp phát bộ nhớ thô. 'Dynamic_cast' cũng vậy. (Xem phiên bản thứ 3 của Stroustrup, Ngôn ngữ lập trình C ++, trang 384 và 385 để biết các ngoại lệ tiêu chuẩn trong C ++.) Trình biên dịch có thể cho phép hành vi này bị vô hiệu hóa. Nhưng nói chung, bạn sẽ phải chịu một số chi phí do các phần mở đầu và ngoại lệ xử lý ngoại lệ được tạo đúng trong mã được tạo, ngay cả khi các ngoại lệ thực sự không diễn ra và ngay cả khi hàm được biên dịch thực sự không có bất kỳ khối xử lý ngoại lệ nào. (Stroustrup đã công khai than thở về điều này.)
Nếu không có chuyên môn mẫu một phần (không phải tất cả các trình biên dịch C ++ đều hỗ trợ nó), việc sử dụng các mẫu có thể đánh vần thảm họa cho lập trình nhúng. Không có nó, mã nở là một rủi ro nghiêm trọng có thể giết chết một dự án nhúng bộ nhớ nhỏ trong nháy mắt.
Khi một hàm C ++ trả về một đối tượng, trình biên dịch không tên tạm thời được tạo và hủy. Một số trình biên dịch C ++ có thể cung cấp mã hiệu quả nếu một hàm tạo đối tượng được sử dụng trong câu lệnh return, thay vì một đối tượng cục bộ, làm giảm nhu cầu xây dựng và phá hủy của một đối tượng. Nhưng không phải mọi trình biên dịch đều thực hiện điều này và nhiều lập trình viên C ++ thậm chí không nhận thức được "tối ưu hóa giá trị trả về" này.
Việc cung cấp một hàm tạo đối tượng với một loại tham số duy nhất có thể cho phép trình biên dịch C ++ tìm đường dẫn chuyển đổi giữa hai loại theo cách hoàn toàn bất ngờ cho lập trình viên. Loại hành vi "thông minh" này không phải là một phần của C.
Mệnh đề bắt chỉ định một loại cơ sở sẽ "cắt" một đối tượng dẫn xuất bị ném, bởi vì đối tượng bị ném được sao chép bằng cách sử dụng "kiểu tĩnh" của mệnh đề bắt chứ không phải "kiểu động" của đối tượng. Một nguồn không phải là hiếm của ngoại lệ (khi bạn cảm thấy bạn thậm chí có thể đủ khả năng ngoại lệ trong mã nhúng của mình.)
Trình biên dịch C ++ có thể tự động tạo các hàm tạo, hàm hủy, sao chép hàm tạo và toán tử gán cho bạn, với các kết quả ngoài ý muốn. Phải mất thời gian để có được cơ sở với các chi tiết của điều này.
Truyền các mảng của các đối tượng dẫn xuất đến một hàm chấp nhận các mảng của các đối tượng cơ sở, hiếm khi tạo ra các cảnh báo của trình biên dịch nhưng hầu như luôn mang lại hành vi không chính xác.
Do C ++ không gọi hàm hủy của các đối tượng được xây dựng một phần khi có ngoại lệ xảy ra trong hàm tạo đối tượng, nên việc xử lý ngoại lệ trong các hàm tạo thường bắt buộc "con trỏ thông minh" để đảm bảo rằng các đoạn được xây dựng trong hàm tạo bị phá hủy đúng cách nếu có ngoại lệ xảy ra ở đó . (Xem Stroustrup, trang 367 và 368.) Đây là vấn đề phổ biến khi viết các lớp tốt trong C ++, nhưng tất nhiên tránh được trong C vì C không có ngữ nghĩa về xây dựng và phá hủy được xây dựng. Viết mã thích hợp để xử lý việc xây dựng của các tiểu dự án trong một đối tượng có nghĩa là viết mã phải đối phó với vấn đề ngữ nghĩa duy nhất này trong C ++; nói cách khác là "viết xung quanh" các hành vi ngữ nghĩa của C ++.
C ++ có thể sao chép các đối tượng được truyền vào tham số đối tượng. Ví dụ: trong các đoạn sau, lệnh gọi "rA (x);" có thể khiến trình biên dịch C ++ gọi hàm tạo cho tham số p, sau đó gọi hàm tạo sao chép để chuyển đối tượng x sang tham số p, sau đó một hàm tạo khác cho đối tượng trả về (tạm thời không tên) của hàm rA, tất nhiên là sao chép từ tham số p. Tồi tệ hơn, nếu lớp A có các vật thể riêng cần xây dựng, điều này có thể gây ra thảm họa. (Lập trình viên AC sẽ tránh được phần lớn rác này, tối ưu hóa tay vì các lập trình viên C không có cú pháp tiện dụng như vậy và phải thể hiện tất cả các chi tiết một lần.)
class A {...};
A rA (A p) { return p; }
// .....
{ A x; rA(x); }
Cuối cùng, một lưu ý ngắn cho lập trình viên C. longjmp () không có hành vi di động trong C ++. (Một số lập trình viên C sử dụng điều này như một loại cơ chế "ngoại lệ".) Một số trình biên dịch C ++ thực sự sẽ cố gắng thiết lập mọi thứ để dọn dẹp khi longjmp được thực hiện, nhưng hành vi đó không khả dụng trong C ++. Nếu trình biên dịch dọn sạch các đối tượng được xây dựng, thì nó không khả dụng. Nếu trình biên dịch không dọn sạch chúng, thì các đối tượng sẽ không bị hủy nếu mã rời khỏi phạm vi của các đối tượng được xây dựng do kết quả của longjmp và hành vi không hợp lệ. (Nếu việc sử dụng longjmp trong foo () không để lại phạm vi, thì hành vi có thể ổn.) Điều này không được sử dụng quá thường xuyên bởi các lập trình viên nhúng C nhưng họ nên tự nhận thức về các vấn đề này trước khi sử dụng chúng.