sử dụng bộ nhớ trong dos dos và don'ts


8

Mặc dù tôi đã viết mã C / C ++ trong một thời gian dài, những hạn chế chưa từng thấy về cách sử dụng bộ nhớ trên các nền tảng lập trình MCU và SOC khác nhau thường làm tôi vấp ngã. Khi tôi chuẩn bị xây dựng mã cho dự án lớn đầu tiên của mình cho các bảng NANO, có khả năng sẽ sử dụng một lượng tài nguyên hợp lý, tôi muốn chuẩn bị tốt hơn cho bất kỳ "vấn đề bất ngờ" bất ngờ và bất ngờ nào để tìm ra .

Ví dụ, trên một SOC khác mà gần đây tôi đã làm việc trên phạm vi rộng (The Pololu.com wixel), tôi đã rất ngạc nhiên khi biết rằng các đối số hàm / phương thức và các biến tự động, mà tôi thường mong đợi được phân bổ và phục hồi khi hàm trả về trong thực tế phân bổ vĩnh viễn cho cuộc sống của chương trình! Ồ Vì vậy, trên nền tảng đó, nơi tôi thường ghét "làm việc quá sức" hoặc sử dụng lại các biến sau khi tên của chúng không còn ý nghĩa, tôi đã phải điều chỉnh mã hóa của mình thành những gì tôi thường coi là BAD để dễ đọc. Không đề cập đến việc nhận ra rằng các biến vòng lặp đơn giản được phân bổ tốt hơn trên toàn cầu. Yecch!

Những người bạn đã gặp phải các vấn đề mã hóa bất ngờ như vậy trong môi trường Arduino có thể chia sẻ một số hướng dẫn "đặc biệt" về những thứ như thế này không?


1
autođã thay đổi từ một công cụ xác định thời lượng lưu trữ thành một công cụ xác định loại biến trong C ++ 11. Để đơn giản, đừng sử dụng auto.
Majenko

1
một bất ngờ tích cực đối với tôi là phân bổ mảng trên ngăn xếp với kích thước từ một biến gcc.gnu.org/onlinesocs/gcc/Variable-Lạng.html
Juraj

Ôi ... trời ơi ... xin lỗi ... tôi chưa bao giờ có ý định suy luận tôi đã từng sử dụng từ khóa 'tự động' (quên mất nó thậm chí còn tồn tại!). Tôi chỉ có nghĩa là bất cứ sự phân bổ mặc định nào cho các vars không tĩnh được khai báo trong một hàm.
Randy

Câu trả lời:


12

Như bạn đã nhận thấy chính mình, điều này có thể phụ thuộc vào nền tảng. Vì bạn đang làm việc trên Nano, câu trả lời của tôi sẽ dành cho kiến ​​trúc AVR, khi được biên dịch bằng gcc, avr-libc và thư viện lõi Arduino.

Trước tiên, hãy để tôi trấn an bạn: hành vi kỳ lạ này bạn thấy về Wixel không xảy ra trên nền tảng này. Các biến cục bộ được phân bổ chủ yếu trong các thanh ghi CPU, sau đó trên ngăn xếp.

Bây giờ, quy tắc chung đầu tiên nghe có vẻ rõ ràng: suy nghĩ kỹ về những gì chương trình của bạn thực sự cần nhớ. Ngoài ra, sử dụng mỗi lần loại dữ liệu nhỏ nhất có thể. Các loại C99 int8_t, uint16_tvà đồng. rất hữu ích cho việc này.

Một quy tắc bạn sẽ thường nghe trên nền tảng này: phân bổ động tránh, ví dụ malloc()new, như khá thường xuyên bạn không thể đủ khả năng phân mảnh heap. Tránh cả Stringlớp, vì nó dựa vào phân bổ động. Thích khi phân bổ tĩnh có thể, hoặc phân bổ ngăn xếp cho những thứ nhỏ.

Đừng quên sử dụng constcho các hằng số: với vòng loại này, trình biên dịch thường có thể tối ưu hóa các hằng số dưới dạng toán hạng ngay lập tức. Đối với mảng hằng, sử dụng PROGMEM. Có, nó không thoải mái khi sử dụng, nhưng nó có thể giúp bạn tiết kiệm rất nhiều RAM. Các F()vĩ mô để in các thông điệp liên tục là một trường hợp đặc biệt của PROGMEM.

Cuối cùng, hãy thật cẩn thận với đệ quy.


Cảm ơn. Điểm tốt! Tôi thường sẽ tránh malloc hoặc 'mới' trong bất kỳ chương trình nhúng nào, đặc biệt là chương trình có thể chạy không được giám sát trong một thời gian dài. Ngoài ra, tôi cũng đã sử dụng bộ nhớ không gian mã cho bất kỳ cấu trúc hoặc mảng nào sẽ không thay đổi. Hoặc thậm chí cấu trúc EEprom cho những thứ như cài đặt. Nhưng thực tế là các vars chức năng được xếp chồng là tin RẤT tốt. Tôi bắt đầu lo lắng khi thấy rất nhiều mã ví dụ, trong đó cùng một tên var được sử dụng lặp đi lặp lại, ngay cả khi tên của nó không còn ý nghĩa nữa.
Randy

Điểm cuối cùng đó cũng thú vị! Tôi tự hỏi nếu một hàm đệ quy vượt quá giới hạn ngăn xếp của nó sẽ làm cho bảng nghèo gần như không thể truy cập được, vì nó sẽ luôn tự chạy vào cùng một lỗ mỗi khi nó thấy sức mạnh. Nó không giống như các thiết bị cũ mà bạn có thể buộc phải dừng lại với một luồng CTRL-C được gửi trong khi bật nguồn!
Randy

1
@Randy: Không có giới hạn ngăn xếp. Nếu bạn tràn ngăn xếp của mình, bạn sẽ bắt đầu ghi đè lên heap của chương trình, nếu có, sau đó là phần .bss, sau đó là phần .data.
Edgar Bonet

1
Sẽ không chính xác khi nói những điều làm hoặc không xảy ra trên một con chip cụ thể khi chúng nằm trong kết quả thực tế của chuỗi công cụ hoặc thậm chí là đặc tả ngôn ngữ , thay vì phần cứng .
Chris Stratton

1
@ChrisStratton: Bạn nói đúng. Tôi đã thêm một sự làm rõ cho câu trả lời của tôi. Mặt khác, AVR được thiết kế với mục đích biên dịch. Sẽ là ngớ ngẩn (mặc dù chắc chắn là có thể) cho một trình biên dịch không sử dụng tệp đăng ký và con trỏ ngăn xếp như chúng được thiết kế để sử dụng.
Edgar Bonet

3

Edgar có một số lời khuyên tuyệt vời và tôi sẽ nhắc lại một lần trước khi đưa ra một vài lời khuyên nữa:

tránh phân bổ bộ nhớ động

Đừng dùng malloc. Khi bạn thiết kế một hệ thống, hãy tiết kiệm RAM như bạn làm ngân sách hàng tháng. Hãy chắc chắn rằng bạn có thể làm mọi thứ cùng một lúc.

Và một vài lời khuyên khác:

thống kê toàn cầu

Tận dụng toàn bộ khối BSS; cố gắng chặn không gian càng nhiều càng tốt trong staticcác mảng toàn cầu . Bạn có thể rùng mình hoặc chế giễu ý tưởng đặt mọi thứ ở cấp độ toàn cầu, nhưng được nhúng là một sự thay đổi mô hình, với bộ quy tắc riêng của nó.

Vì bạn sẽ có rất nhiều biến ở cấp độ toàn cầu, staticgiúp bạn tránh làm ô nhiễm không gian tên của bạn quá nhiều.

tránh các tính năng C ++ ưa thích

Trong thực tế, cố gắng không sử dụng C ++. C ++ đưa bạn "xa hơn kim loại", với RAII và các nhà xây dựng của nó và chuyển đổi loại ngầm của nó ... Khó hơn nhiều để giữ một kho lưu trữ chặt chẽ tất cả các tài nguyên của bạn khi ngôn ngữ của bạn làm quá nhiều điều phía sau. Nếu bạn thực sự muốn sử dụng một tính năng ngôn ngữ C ++ nhất định, hãy cố gắng giữ âm của mã giống như "C với các bit của C ++" thay vì "C ++ được đưa vào một hệ thống nhúng". Điều đó không bao giờ diễn ra tốt đẹp.

Đặc biệt tránh thuốc generic / mẫu, sẽ ăn nhiều bộ nhớ của bạn. Mỗi khi bạn sử dụng một loại mới với một mẫu trong mã của bạn, một bản sao khác của lớp mẫu được biên dịch vào chương trình của bạn, chỉ cho một loại đó. Bạn có bao giờ tự hỏi tại sao các tệp nhị phân C ++ có thể tăng đến 20, 50, thậm chí 100 MB không? Mẫu, người đàn ông. Mẫu.

hăng sô

Từ khóa này nên đi càng nhiều biến càng tốt. Giữ thói quen này sẽ tiết kiệm cả thời gian thực hiện và tiêu thụ không gian. Tìm hiểu sự khác biệt giữa const T* foo, T* const fooconst T* const foo.


1
Quá nhiều điều này là vô nghĩa để bỏ qua. Một số điều này là tốt, nhưng khuyến nghị một loạt các trạng thái toàn cầu chỉ là lời khuyên tồi và nhiều người sử dụng C ++ cho loại lập trình này. Nhúng không phải là một cái cớ để viết khó để duy trì mã.
RubberDuck

3
Ngoài ra, các mẫu C ++ không cần phải quá nặng như bạn làm. Điều quan trọng là đảm bảo bạn biên dịch với tối ưu hóa thời gian liên kết, để ngăn từng tệp đối tượng riêng lẻ có bản sao riêng của từng chuyên môn mẫu mà nó sử dụng. 10 bản sao khác nhau vector<int>, một trong mỗi 10 tệp nguồn, có một chút khó sử dụng, nhưng với LTO, trình biên dịch / trình liên kết có thể sao chép chúng thành một bản sao được chia sẻ. Nếu bạn cần chức năng, mã chuyên biệt của mẫu (đặc biệt là sau khi nội tuyến LTO) thường có kích thước khá giống với những gì bạn viết bằng tay.
ShadowRanger

3
Không tạo một biến toàn cục nếu nó chỉ được sử dụng trong một hàm. Làm cho nó staticnếu giá trị cần được ghi nhớ từ lời mời đến lời mời. Phân bổ-khôn ngoan, một địa phương tĩnh giống như toàn cầu.
Edgar Bonet

Cảm ơn vì những lời khuyên. Tôi thấy công đức trong một số điều này cho các hệ thống nhỏ. Mặc dù tôi đã thấy các trình biên dịch C ++ đi một chặng đường dài, tôi vẫn miễn cưỡng sử dụng C ++ "thuần túy" và được OOP 100% trong mọi góc của mã. Đôi khi nó quá mức cần thiết. Tuy nhiên, việc xây dựng chức năng được thiết lập tốt thành các lớp bạn có thể giấu trong thư viện sẽ giúp ứng dụng của bạn đơn giản hơn rất nhiều để làm theo.
Randy
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.