Khả năng phân bổ bộ nhớ cho thiết kế phần sụn trong C


16

phương pháp tiếp cận mô-đun khá tiện dụng nói chung (di động và sạch sẽ), vì vậy tôi cố gắng lập trình các mô-đun độc lập với bất kỳ mô-đun nào khác có thể. Hầu hết các cách tiếp cận của tôi dựa trên một cấu trúc mô tả chính mô-đun. Hàm khởi tạo đặt các tham số chính, sau đó một trình xử lý (con trỏ tới cấu trúc mô tả) được truyền cho bất kỳ hàm nào bên trong mô-đun được gọi.

Ngay bây giờ, tôi tự hỏi cách tiếp cận tốt nhất của bộ nhớ phân bổ cho cấu trúc mô tả một mô-đun có thể là gì. Nếu có thể, tôi muốn như sau:

  • Cấu trúc mờ, do đó, cấu trúc chỉ có thể được thay đổi bằng cách sử dụng các chức năng giao diện được cung cấp
  • Nhiều trường hợp
  • bộ nhớ được phân bổ bởi liên kết

Tôi thấy các khả năng sau đây, rằng tất cả xung đột với một trong những mục tiêu của tôi:

tuyên bố toàn cầu

nhiều trường hợp, được phân bổ bởi trình liên kết, nhưng struct không mờ

(#includes)
module_struct module;

void main(){
   module_init(&module);
}

trung tâm thương mại

cấu trúc mờ, nhiều trường hợp, nhưng allcotion trên heap

trong mô-đun.h:

typedef module_struct Module;

trong hàm module.c init, malloc và trả về con trỏ tới bộ nhớ được cấp phát

module_mem = malloc(sizeof(module_struct ));
/* initialize values here */
return module_mem;

trong main.c

(#includes)
Module *module;

void main(){
    module = module_init();
}

khai báo trong mô-đun

cấu trúc mờ, được phân bổ bởi trình liên kết, chỉ một số trường hợp được xác định trước

giữ toàn bộ cấu trúc và bộ nhớ trong cho mô-đun và không bao giờ để lộ trình xử lý hoặc cấu trúc.

(#includes)

void main(){
    module_init(_no_param_or_index_if_multiple_instances_possible_);
}

Có một tùy chọn để kết hợp những thứ này bằng cách nào đó cho cấu trúc mờ, trình liên kết thay vì phân bổ heap và nhiều / bất kỳ số lượng phiên bản nào không?

giải pháp

như đề xuất trong một số câu trả lời dưới đây, tôi nghĩ cách tốt nhất là:

  1. không gian dự trữ cho các mô-đun MODULE_MAX_INSTANCE_COUNT trong tệp nguồn mô-đun
  2. không xác định MODULE_MAX_INSTANCE_COUNT trong chính mô-đun
  3. thêm một #ifndef MODULE_MAX_INSTANCE_COUNT #error vào tệp tiêu đề mô-đun để đảm bảo người dùng mô-đun nhận thức được giới hạn này và xác định số lượng phiên bản tối đa muốn cho ứng dụng
  4. khi khởi tạo một thể hiện trả về địa chỉ bộ nhớ (* void) của cấu trúc mô tả hoặc chỉ mục mô-đun (bất cứ điều gì bạn thích hơn)

12
Hầu hết các nhà thiết kế FW nhúng đều tránh phân bổ động để giữ cho việc sử dụng bộ nhớ mang tính quyết định và đơn giản. Đặc biệt nếu nó là kim loại trần và không có HĐH cơ bản để quản lý bộ nhớ.
Eugene Sh.

Chính xác, đó là lý do tại sao tôi muốn trình liên kết thực hiện phân bổ.
L. Heinrichs

4
Tôi không chắc là tôi hiểu ... Làm thế nào bạn có thể có bộ nhớ được cấp phát bởi trình liên kết nếu bạn có số lượng phiên bản động? Điều đó có vẻ khá trực giao với tôi.
jcaron

Tại sao không để trình liên kết phân bổ một nhóm bộ nhớ lớn và thực hiện phân bổ của riêng bạn từ đó, điều này cũng mang lại cho bạn lợi ích của phân bổ không chi phí. Bạn có thể làm cho đối tượng pool tĩnh thành tệp với hàm phân bổ để nó ở chế độ riêng tư. Trong một số mã của tôi, tôi thực hiện tất cả các phân bổ trong các thói quen init khác nhau, sau đó tôi in ra sau đó đã phân bổ bao nhiêu, vì vậy, trong phần biên dịch sản xuất cuối cùng, tôi đặt nhóm theo kích thước chính xác đó.
Lee Daniel Crocker

2
Nếu đó là quyết định thời gian biên dịch, bạn có thể chỉ cần xác định số trong Makefile hoặc tương đương, và bạn đã hoàn tất. Số này sẽ không nằm trong nguồn của mô-đun, nhưng sẽ là ứng dụng cụ thể và bạn chỉ cần sử dụng số hiệu làm tham số.
jcaron

Câu trả lời:


4

Có một tùy chọn để kết hợp những thứ này bằng cách nào đó cho struct, linker ẩn danh thay vì phân bổ heap và nhiều / bất kỳ số lượng phiên bản nào không?

Chắc chắn là có. Tuy nhiên, trước tiên, nhận ra rằng "bất kỳ số lượng" nào của các thể hiện phải được sửa hoặc ít nhất là một giới hạn trên được thiết lập, tại thời điểm biên dịch. Đây là điều kiện tiên quyết để các trường hợp được phân bổ tĩnh (cái mà bạn gọi là "phân bổ liên kết"). Bạn có thể điều chỉnh số mà không cần sửa đổi nguồn bằng cách khai báo một macro chỉ định nó.

Sau đó, tệp nguồn chứa khai báo cấu trúc thực tế và tất cả các hàm liên quan của nó cũng khai báo một mảng các trường hợp có liên kết bên trong. Nó cung cấp một mảng, với liên kết ngoài, của các con trỏ tới các thể hiện hoặc một hàm khác để truy cập các con trỏ khác nhau theo chỉ mục. Các biến thể chức năng là một chút mô-đun:

mô-đun.c

#include <module.h>

// 4 instances by default; can be overridden at compile time
#ifndef NUM_MODULE_INSTANCES
#define NUM_MODULE_INSTANCES 4
#endif

struct module {
    int demo;
};

// has internal linkage, so is not directly visible from other files:
static struct module instances[NUM_MODULE_INSTANCES];

// module functions

struct module *module_init(unsigned index) {
    instances[index].demo = 42;
    return &instances[index];
}

Tôi đoán bạn đã quen thuộc với cách tiêu đề sau đó sẽ khai báo struct là một kiểu không hoàn chỉnh và khai báo tất cả các hàm (được viết dưới dạng con trỏ cho kiểu đó). Ví dụ:

mô-đun

#ifndef MODULE_H
#define MODULE_H

struct module;

struct module *module_init(unsigned index);

// other functions ...

#endif

Bây giờ struct modulemờ đục trong các đơn vị dịch thuật khác module.c, * và bạn có thể truy cập và sử dụng tối đa số lượng phiên bản được xác định tại thời điểm biên dịch mà không cần phân bổ động.


* Trừ khi bạn sao chép định nghĩa của nó, tất nhiên. Vấn đề là module.hkhông làm điều đó.


Tôi nghĩ đó là một thiết kế kỳ quặc để vượt qua chỉ số từ bên ngoài lớp học. Khi tôi triển khai các nhóm bộ nhớ như thế này, tôi để chỉ mục là một bộ đếm riêng, tăng thêm 1 cho mỗi phiên bản được phân bổ. Cho đến khi bạn đạt đến "NUM_MODULE_INSTANCES", trong đó hàm tạo sẽ trả về lỗi hết bộ nhớ.
Lundin

Đó là một điểm công bằng, @Lundin. Khía cạnh đó của thiết kế cho rằng các chỉ mục có ý nghĩa vốn có, trong thực tế có thể hoặc không phải là trường hợp. Đó trường hợp, mặc dù tầm thường như vậy, cho trường hợp bắt đầu của OP. Tầm quan trọng như vậy, nếu nó tồn tại, có thể được hỗ trợ thêm bằng cách cung cấp một phương tiện để có được một con trỏ cá thể mà không cần khởi tạo.
John Bollinger

Vì vậy, về cơ bản, bạn dự trữ bộ nhớ cho n mô-đun, bất kể sẽ sử dụng bao nhiêu, sau đó trả về một con trỏ cho phần tử không sử dụng tiếp theo nếu ứng dụng khởi chạy nó. Tôi đoán rằng có thể làm việc.
L. Heinrichs

@ L.Heinrichs Có, vì các hệ thống nhúng có tính chất quyết định. Không có thứ gọi là "lượng vật thể vô tận" hay "lượng vật thể không xác định". Các đối tượng cũng thường là các singletons (trình điều khiển phần cứng), do đó thường không cần nhóm bộ nhớ, vì chỉ một trường hợp duy nhất của đối tượng sẽ tồn tại.
Lundin

Tôi đồng ý cho hầu hết các trường hợp. Câu hỏi có một số lợi ích lý thuyết là tốt. Nhưng tôi có thể sử dụng hàng trăm cảm biến nhiệt độ 1 dây nếu có đủ IOs (như một ví dụ tôi có thể đưa ra bây giờ).
L. Heinrichs

22

Tôi lập trình các bộ điều khiển vi mô nhỏ trong C ++, để đạt được chính xác những gì bạn muốn.

Những gì bạn gọi một mô-đun là một lớp C ++, nó có thể chứa dữ liệu (có thể truy cập bên ngoài hoặc không) và các chức năng (tương tự). Hàm tạo (một hàm chuyên dụng) khởi tạo nó. Hàm tạo có thể lấy tham số thời gian chạy hoặc tham số (yêu thích) biên dịch thời gian biên dịch (mẫu). Các hàm trong lớp mặc nhiên lấy biến lớp làm tham số đầu tiên. (Hoặc, thường là sở thích của tôi, lớp có thể hoạt động như một singleton ẩn, vì vậy tất cả dữ liệu được truy cập mà không cần chi phí này).

Đối tượng lớp có thể là toàn cục (vì vậy bạn biết tại thời điểm liên kết rằng mọi thứ sẽ phù hợp) hoặc stack-local, có lẽ là chính. (Tôi không thích toàn cầu C ++ vì thứ tự khởi tạo toàn cầu không xác định, vì vậy tôi thích stack-local).

Phong cách lập trình ưa thích của tôi là các mô-đun là các lớp tĩnh và cấu hình (tĩnh) của chúng là theo các tham số mẫu. Điều này tránh gần như tất cả quá mức và cho phép tối ưu hóa. Kết hợp điều này với một công cụ tính toán kích thước ngăn xếp và bạn có thể ngủ mà không phải lo lắng :)

Bài nói chuyện của tôi về cách mã hóa này trong C ++: Đối tượng? Không, cám ơn!

Rất nhiều lập trình viên nhúng / vi điều khiển dường như không thích C ++ vì họ nghĩ rằng nó sẽ buộc họ phải sử dụng tất cả C ++. Điều đó hoàn toàn không cần thiết, và sẽ là một ý tưởng rất tồi. (Bạn cũng có thể không sử dụng tất cả C! Hãy nghĩ heap, dấu phẩy động, setjmp / longjmp, printf, ...)


Trong một bình luận Adam Haun đề cập đến RAII và khởi tạo. IMO RAII có liên quan nhiều hơn đến việc giải cấu trúc, nhưng quan điểm của ông là hợp lệ: các đối tượng toàn cầu sẽ được xây dựng trước khi bắt đầu chính của bạn, vì vậy chúng có thể hoạt động dựa trên các giả định không hợp lệ (như tốc độ đồng hồ chính sẽ được thay đổi sau đó). Đó là một lý do nữa KHÔNG sử dụng các đối tượng khởi tạo mã toàn cầu. (Tôi sử dụng tập lệnh liên kết sẽ thất bại khi tôi có các đối tượng khởi tạo mã toàn cầu.) IMO các 'đối tượng' như vậy cần được tạo rõ ràng và chuyển xung quanh. Điều này bao gồm một đối tượng 'cơ sở' đang chờ 'cung cấp hàm Wait (). Trong thiết lập của tôi, đây là 'đối tượng' đặt tốc độ xung nhịp của chip.

Nói về RAII: đó là một tính năng C ++ khác rất hữu ích trong các hệ thống nhúng nhỏ, mặc dù không phải vì lý do (giải quyết bộ nhớ) mà nó được sử dụng nhiều nhất trong các hệ thống lớn (các hệ thống nhúng nhỏ hầu như không sử dụng giải quyết bộ nhớ động). Hãy nghĩ đến việc khóa tài nguyên: bạn có thể biến tài nguyên bị khóa thành đối tượng trình bao bọc và hạn chế quyền truy cập vào tài nguyên chỉ có thể thông qua trình bao bọc khóa. Khi trình bao bọc đi ra khỏi phạm vi, tài nguyên được mở khóa. Điều này ngăn chặn truy cập mà không khóa, và làm cho nó khó có khả năng quên mở khóa hơn. với một số phép thuật (mẫu), nó có thể là không chi phí.


Câu hỏi ban đầu không đề cập đến C, do đó câu trả lời trung tâm C ++ của tôi. Nếu nó thực sự phải là C ....

Bạn có thể sử dụng thủ thuật macro: khai báo công khai các cột của bạn, để chúng có một loại và có thể được phân bổ trên toàn cầu, nhưng mang tên của các thành phần của chúng vượt quá khả năng sử dụng, trừ khi một số macro được định nghĩa khác, đó là trường hợp trong tệp .c của mô-đun của bạn. Để bảo mật hơn, bạn có thể sử dụng thời gian biên dịch trong xáo trộn.

Hoặc có phiên bản công khai của cấu trúc của bạn không có gì hữu ích trong đó và chỉ có phiên bản riêng (với dữ liệu hữu ích) trong tệp .c của bạn và khẳng định rằng chúng có cùng kích thước. Một chút thủ thuật make-file có thể tự động hóa việc này.


@Lundins bình luận về các lập trình viên (nhúng) xấu:

  • Kiểu lập trình viên mà bạn mô tả có thể sẽ tạo ra một mớ hỗn độn trong bất kỳ ngôn ngữ nào. Macro (hiện diện trong C và C ++) là một cách rõ ràng.

  • Dụng cụ có thể giúp một phần nào đó. Đối với học sinh của mình, tôi bắt buộc một tập lệnh được xây dựng chỉ định không có ngoại lệ, không có rtti và đưa ra lỗi liên kết khi có cả heap được sử dụng hoặc toàn cầu khởi tạo mã. Và nó chỉ định cảnh báo = lỗi và cho phép gần như tất cả các cảnh báo.

  • Tôi khuyến khích sử dụng các mẫu, nhưng với constexpr và các khái niệm siêu lập trình thì ngày càng ít yêu cầu hơn.

  • "Lập trình viên Arduino bối rối" Tôi rất muốn thay thế kiểu lập trình Arduino (nối dây, sao chép mã trong thư viện) bằng cách tiếp cận C ++ hiện đại, có thể dễ dàng hơn, an toàn hơn và tạo mã nhanh hơn và nhỏ hơn. Giá như tôi có thời gian và sức mạnh ....


Cảm ơn câu trả lời này! Sử dụng C ++ là một tùy chọn, nhưng chúng tôi đang sử dụng C trong công ty của tôi (những gì tôi chưa đề cập rõ ràng). Tôi đã cập nhật câu hỏi để cho mọi người biết tôi đang nói về C.
L. Heinrichs

Tại sao bạn sử dụng (chỉ) C? Có lẽ điều này mang lại cho bạn cơ hội để thuyết phục họ ít nhất là xem xét C ++ ... Điều bạn muốn là điều cần thiết (một phần nhỏ) C ++ nhận ra ở C.
Wouter van Ooijen

Những gì tôi làm trong dự án sở thích nhúng 'thực sự' đầu tiên của tôi) là khởi tạo mặc định đơn giản trong hàm tạo và sử dụng một phương thức init riêng cho các lớp có liên quan. Một lợi ích khác là tôi có thể vượt qua các con trỏ còn sơ khai để thử nghiệm đơn vị.
Michel Keijzers

2
@Michel cho một dự án sở thích bạn có thể tự do lựa chọn ngôn ngữ? Hãy dùng C ++!
Wouter van Ooijen

4
Và trong khi nó thực sự là hoàn toàn có thể viết chương trình tốt C ++ cho nhúng, vấn đề là một số> 50% của tất cả các lập trình viên hệ thống nhúng ra có quacks, các lập trình viên máy tính bối rối, Arduino người có sở thích vv vv Những loại người chỉ đơn giản là không thể sử dụng một tập hợp con sạch của C ++, ngay cả khi bạn giải thích nó với khuôn mặt của họ. Cung cấp cho họ C ++ và trước khi bạn biết điều đó, họ sẽ sử dụng toàn bộ STL, lập trình siêu mẫu, xử lý ngoại lệ, nhiều kế thừa, v.v. Và kết quả tất nhiên là rác hoàn chỉnh. Đáng buồn thay, đây là cách mà 8 trong số 10 dự án C ++ nhúng kết thúc.
Lundin

7

Tôi tin rằng FreeRTOS (có thể là một hệ điều hành khác?) Thực hiện một cái gì đó giống như những gì bạn đang tìm kiếm bằng cách xác định 2 phiên bản khác nhau của cấu trúc.
Cái 'thật', được sử dụng bên trong bởi các chức năng của hệ điều hành và một cái 'giả' có cùng kích thước với cái 'thật', nhưng không có bất kỳ thành viên hữu ích nào bên trong (chỉ có một nhóm int dummy1và tương tự).
Chỉ cấu trúc 'giả' được hiển thị bên ngoài mã hệ điều hành và điều này được sử dụng để phân bổ bộ nhớ cho các phiên bản tĩnh của cấu trúc.
Trong nội bộ, khi các chức năng trong HĐH được gọi, chúng được chuyển qua địa chỉ của cấu trúc 'giả' bên ngoài như một tay cầm và sau đó đây là kiểu chữ như một con trỏ tới cấu trúc 'thực' để các chức năng của HĐH có thể làm những gì chúng cần làm


Ý kiến ​​hay, tôi đoán tôi có thể sử dụng --- #define BUILD_BUG_ON (điều kiện) ((void) sizeof (char [1 - 2 * !! (điều kiện)])) --- BUILD_BUG_ON (sizeof (real_struct)! = Sizeof ( fake_struct)) ----
L. Heinrichs

2

Cấu trúc ẩn danh, do đó, cấu trúc chỉ có thể được thay đổi bằng cách sử dụng các chức năng giao diện được cung cấp

Theo tôi, điều này là vô nghĩa. Bạn có thể đặt một bình luận ở đó, nhưng không có điểm nào cố gắng để che giấu nó thêm.

C sẽ không bao giờ cung cấp sự cô lập cao như vậy, ngay cả khi không có khai báo cho struct, nó sẽ dễ dàng vô tình ghi đè lên nó với ví dụ nhầm memcpy () hoặc tràn bộ đệm.

Thay vào đó, chỉ cần đặt tên cho struct và tin tưởng người khác viết mã tốt. Nó cũng sẽ làm cho việc gỡ lỗi dễ dàng hơn khi struct có tên mà bạn có thể sử dụng để chỉ nó.


2

Các câu hỏi SW thuần túy được hỏi tốt hơn tại /programming/ .

Khái niệm phơi bày một cấu trúc kiểu không hoàn chỉnh cho người gọi, như bạn mô tả, thường được gọi là "loại mờ" hoặc "con trỏ mờ" - struct ẩn có nghĩa là một cái gì đó hoàn toàn khác.

Vấn đề với điều này là người gọi sẽ không thể phân bổ các thể hiện của đối tượng, chỉ con trỏ đến nó. Trên PC, bạn sẽ sử dụng mallocbên trong "constructor" của đối tượng, nhưng malloc là không có trong các hệ thống nhúng.

Vì vậy, những gì bạn làm trong nhúng là cung cấp một nhóm bộ nhớ. Bạn có một lượng RAM hạn chế, vì vậy việc hạn chế số lượng đối tượng có thể được tạo ra thường không phải là vấn đề.

Xem Phân bổ tĩnh các loại dữ liệu mờ tại SO.


Ồ cảm ơn bạn đã làm rõ sự nhầm lẫn đặt tên ở cuối của tôi, tôi điều chỉnh OP. Tôi đã nghĩ đến việc xếp chồng tràn, nhưng quyết định tôi muốn nhắm mục tiêu cụ thể các lập trình viên nhúng.
L. Heinrichs
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.