C thiết kế máy trạng thái [đóng]


193

Tôi đang tạo một dự án nhỏ trong hỗn hợp C và C ++. Tôi đang xây dựng một cỗ máy nhà nước nhỏ ở trung tâm của một trong những chủ đề công nhân của tôi.

Tôi đã tự hỏi nếu bạn có kinh nghiệm về SO sẽ chia sẻ các kỹ thuật thiết kế máy nhà nước của bạn.

LƯU Ý: Tôi chủ yếu sau khi thử và kiểm tra các kỹ thuật thực hiện.

CẬP NHẬT: Dựa trên tất cả các đầu vào tuyệt vời được thu thập trên SO, tôi đã giải quyết kiến ​​trúc này:

Một máy bơm sự kiện trỏ đến một nhà tích hợp sự kiện chỉ đến một người điều phối.  Bộ điều phối chỉ từ 1 đến n hành động quay lại bộ tích hợp sự kiện.  Một bảng chuyển tiếp với các ký tự đại diện chỉ vào bộ điều phối.


4
Các câu trả lời ở đây là rất tốt. Ngoài ra, hãy xem câu hỏi trùng lặp này cũng có một số câu trả lời hay: stackoverflow.com/questions/1371460/state-machines-tutorials
Michael Burr



Câu trả lời:


170

Các máy trạng thái mà tôi đã thiết kế trước đó (C, không phải C ++) đều đi xuống một structmảng và một vòng lặp. Cấu trúc về cơ bản bao gồm một trạng thái và sự kiện (để tra cứu) và một hàm trả về trạng thái mới, đại loại như:

typedef struct {
    int st;
    int ev;
    int (*fn)(void);
} tTransition;

Sau đó, bạn xác định trạng thái và sự kiện của mình bằng các định nghĩa đơn giản (các trạng thái ANYlà các dấu đặc biệt, xem bên dưới):

#define ST_ANY              -1
#define ST_INIT              0
#define ST_ERROR             1
#define ST_TERM              2
: :
#define EV_ANY              -1
#define EV_KEYPRESS       5000
#define EV_MOUSEMOVE      5001

Sau đó, bạn xác định tất cả các chức năng được gọi bởi các chuyển đổi:

static int GotKey (void) { ... };
static int FsmError (void) { ... };

Tất cả các hàm này được viết để không có biến và trả về trạng thái mới cho máy trạng thái. Trong ví dụ này, các biến toàn cục được sử dụng để truyền bất kỳ thông tin nào vào các hàm trạng thái khi cần thiết.

Sử dụng toàn cầu không tệ như âm thanh vì FSM thường bị khóa trong một đơn vị biên dịch duy nhất và tất cả các biến là tĩnh đối với đơn vị đó (đó là lý do tại sao tôi sử dụng các trích dẫn xung quanh "toàn cầu" ở trên - chúng được chia sẻ nhiều hơn trong FSM, hơn cả thực sự toàn cầu). Như với tất cả các toàn cầu, nó đòi hỏi phải chăm sóc.

Sau đó, mảng chuyển tiếp xác định tất cả các chuyển đổi có thể và các hàm được gọi cho các chuyển đổi đó (bao gồm cả chuyển tiếp bắt tất cả):

tTransition trans[] = {
    { ST_INIT, EV_KEYPRESS, &GotKey},
    : :
    { ST_ANY, EV_ANY, &FsmError}
};
#define TRANS_COUNT (sizeof(trans)/sizeof(*trans))

Điều đó có nghĩa là: nếu bạn ở trong ST_INITbang và bạn nhận được EV_KEYPRESSsự kiện, hãy gọi điện tới GotKey.

Hoạt động của FSM sau đó trở thành một vòng lặp tương đối đơn giản:

state = ST_INIT;
while (state != ST_TERM) {
    event = GetNextEvent();
    for (i = 0; i < TRANS_COUNT; i++) {
        if ((state == trans[i].st) || (ST_ANY == trans[i].st)) {
            if ((event == trans[i].ev) || (EV_ANY == trans[i].ev)) {
                state = (trans[i].fn)();
                break;
            }
        }
    }
}

Như đã nói ở trên, lưu ý việc sử dụng các ký ST_ANYtự đại diện, cho phép một sự kiện gọi một hàm bất kể trạng thái hiện tại. EV_ANYcũng hoạt động tương tự, cho phép bất kỳ sự kiện nào ở một trạng thái cụ thể gọi hàm.

Nó cũng có thể đảm bảo rằng, nếu bạn đến cuối mảng chuyển tiếp, bạn sẽ gặp lỗi cho biết FSM của bạn chưa được xây dựng chính xác (bằng cách sử dụng ST_ANY/EV_ANYkết hợp.

Tôi đã sử dụng mã tương tự cho điều này trên nhiều dự án truyền thông lớn, chẳng hạn như triển khai sớm các ngăn xếp và giao thức truyền thông cho các hệ thống nhúng. Ưu điểm lớn là sự đơn giản và tương đối dễ dàng trong việc thay đổi mảng chuyển tiếp.

Tôi chắc chắn sẽ có những khái niệm trừu tượng cấp cao hơn có thể phù hợp hơn hiện nay nhưng tôi nghi ngờ tất cả chúng sẽ sôi sục với cùng một loại cấu trúc này.


Và, như ldogcác trạng thái trong một nhận xét, bạn có thể tránh các quả cầu hoàn toàn bằng cách chuyển một con trỏ cấu trúc cho tất cả các hàm (và sử dụng nó trong vòng lặp sự kiện). Điều này sẽ cho phép nhiều máy trạng thái chạy song song mà không bị nhiễu.

Chỉ cần tạo một kiểu cấu trúc chứa dữ liệu dành riêng cho máy (trạng thái ở mức tối thiểu) và sử dụng dữ liệu đó thay vì toàn cầu.

Lý do tôi hiếm khi thực hiện điều đó đơn giản là vì hầu hết các máy trạng thái mà tôi đã viết đều là loại đơn (ví dụ một lần, lúc bắt đầu xử lý, đọc tệp cấu hình chẳng hạn), không cần chạy nhiều hơn một phiên bản . Nhưng nó có giá trị nếu bạn cần chạy nhiều hơn một.


24
Một công tắc khổng lồ trộn mã với FSM. Ngay cả khi chỉ có một lệnh gọi chức năng cho mỗi lần chuyển đổi, vẫn có một số mã và ai đó dễ dàng lạm dụng điều đó bằng cách chỉ thêm một dòng chuyển tiếp 4 dòng nhỏ. hen một dòng mười. Sau đó, nó ra khỏi tầm tay. Với mảng struct, FSM luôn sạch sẽ - bạn có thể thấy mọi chuyển đổi và hiệu ứng (chức năng). Và tôi đã bắt đầu khi enums là một nháy mắt trong ISO, viết mã cho 6809 nền tảng nhúng với các trình biên dịch, chúng ta sẽ nói, chưa hoàn hảo :-)
paxdiablo

5
Bạn nói đúng, enums sẽ tốt hơn, nhưng tôi vẫn thích có FSM như một mảng cấu trúc. Sau đó, tất cả được điều hành bởi dữ liệu chứ không phải mã (vâng, có một số mã nhưng cơ hội nhồi nhét vòng lặp FSM mà tôi đã đưa ra là rất nhỏ).
paxdiablo

2
Điều này là tốt, đối với các máy trạng thái đối lập quy trình mà tôi đã sử dụng để luôn thêm ba trạm biến áp (có thể trống) cho mọi trạng thái, để lệnh gọi hàm chức năng sẽ trở thành GotKey (trạm biến áp), trong đó trạm biến áp sẽ là: - SS_ENTRY - SS_RUN - SS_EXIT Về cơ bản, chức năng trạng thái được gọi với một trạm biến áp SS_ENTRY khi vào, để trạng thái có thể xây dựng lại trạng thái (ví dụ: vị trí bộ truyền động). Mặc dù không có chuyển đổi, giá trị thay thế SS_RUN được thông qua. Khi chuyển đổi, hàm trạng thái được gọi với trạm biến áp SS_EXIT, để nó có thể thực hiện bất kỳ việc dọn dẹp nào (ví dụ: giải phóng tài nguyên).
Metiu

13
Bạn đã đề cập rằng bạn chia sẻ dữ liệu bằng cách sử dụng toàn cầu, nhưng có lẽ sẽ sạch hơn nếu bạn xác định các hàm trạng thái là int (*fn)(void*);nơi void*con trỏ tới dữ liệu mà mỗi hàm trạng thái lấy làm tham số. Sau đó, các hàm trạng thái có thể sử dụng dữ liệu hoặc bỏ qua chúng.
ldog

13
Tôi sử dụng cùng một phân tách dữ liệu / mã để viết các FSM, ngoại trừ việc tôi không bao giờ xảy ra để giới thiệu các trạng thái 'ký tự đại diện'. Ý tưởng thú vị! Tuy nhiên, việc lặp lại mảng chuyển đổi có thể trở nên đắt đỏ nếu bạn có nhiều trạng thái (đó là trường hợp đối với tôi vì mã C được tạo tự động). Trong các tình huống như vậy, sẽ hiệu quả hơn khi có một mảng chuyển đổi cho mỗi trạng thái. Vì vậy, một trạng thái không còn là một giá trị enum, mà là một bảng chuyển tiếp. Bằng cách đó, bạn không phải lặp lại tất cả các chuyển đổi trong máy mà chỉ là những chuyển đổi có liên quan đến trạng thái hiện tại.
Frerich Raabe

78

Các câu trả lời khác là tốt, nhưng cách triển khai rất "nhẹ" mà tôi đã sử dụng khi máy trạng thái trông rất đơn giản như sau:

enum state { ST_NEW, ST_OPEN, ST_SHIFT, ST_END };

enum state current_state = ST_NEW;

while (current_state != ST_END)
{
    input = get_input();

    switch (current_state)
    {
        case ST_NEW:
        /* Do something with input and set current_state */
        break;

        case ST_OPEN:
        /* Do something different and set current_state */
        break;

        /* ... etc ... */
    }
}

Tôi sẽ sử dụng điều này khi máy trạng thái đủ đơn giản để tiếp cận bảng chuyển đổi trạng thái và con trỏ hàm quá mức. Điều này thường hữu ích cho phân tích từng ký tự hoặc từng chữ.


37

Xin lỗi vì tôi đã phá vỡ mọi quy tắc trong khoa học máy tính, nhưng máy trạng thái là một trong số ít (tôi chỉ có thể đếm hai lần) trong đó một gotocâu lệnh không chỉ hiệu quả hơn mà còn giúp mã của bạn sạch hơn và dễ đọc hơn. Bởi vìgoto câu lệnh dựa trên nhãn, bạn có thể đặt tên cho các trạng thái của mình thay vì phải theo dõi một mớ hỗn độn của các con số hoặc sử dụng một enum. Nó cũng làm cho mã sạch hơn nhiều vì bạn không cần tất cả các chuỗi con trỏ hàm bổ sung hoặc các câu lệnh chuyển đổi lớn và trong khi các vòng lặp. Tôi đã đề cập đến nó hiệu quả hơn?

Đây là những gì một máy trạng thái có thể trông như thế nào:

void state_machine() {
first_state:
    // Do some stuff here
    switch(some_var) {
    case 0:
        goto first_state;
    case 1:
        goto second_state;
    default:
        return;
    }

second_state:
    // Do some stuff here
    switch(some_var) {
    case 0:
        goto first_state;
    case 1:
        goto second_state;
    default:
        return;
    }
}

Bạn nhận được các ý tưởng chung. Vấn đề là bạn có thể thực hiện máy trạng thái một cách hiệu quả và một máy tương đối dễ đọc và hét lên với người đọc rằng họ đang nhìn vào một máy trạng thái. Lưu ý rằng nếu bạn đang sử dụng các gotocâu lệnh, bạn vẫn phải cẩn thận vì rất dễ tự bắn vào chân mình trong khi thực hiện.


4
Điều này chỉ hoạt động nếu máy trạng thái nằm trong đối tượng cấp cao nhất. Khoảnh khắc một số đối tượng khác thỉnh thoảng được thăm dò / gửi tin nhắn đến, cần phải có trạng thái, bạn bị mắc kẹt với cách tiếp cận này (hoặc bạn phải làm cho nó phức tạp hơn nhiều)
skrebbel

1
Điều này thực sự buộc bạn phải sử dụng đa nhiệm phủ đầu trong tất cả các trường hợp đơn giản nhất.
Craig McQueen

1
Những gotos có thể được thay thế bằng các cuộc gọi chức năng. Và nếu một trình hồ sơ cho bạn biết rằng chương trình của bạn bị chết đuối do chức năng gọi qua chức năng, thì bạn có thể thay thế các cuộc gọi bằng gotos khi cần thiết.
Abtin Forouzandeh

7
@AbtinForouzandeh chỉ cần thay thế gotos bằng các lệnh gọi hàm sẽ gây ra luồng stackover vì ngăn xếp cuộc gọi chỉ bị xóa trong trường hợp có lỗi.
JustMaximumPower

Tôi đồng ý với phương pháp goto. Dưới đây là một bộ các macro minh họa điều đó. Và các macro làm cho mã của bạn có cấu trúc như thể bạn đã mã hóa nó như bình thường. Nó cũng hoạt động ở mức ngắt, thường là nơi cần có máy trạng thái codeproject.com/Articles/37037/ Lời
eddyq

30

Bạn có thể xem xét Trình biên dịch máy trạng thái http://smc.sourceforge.net/

Tiện ích mã nguồn mở tuyệt vời này chấp nhận mô tả về một máy trạng thái bằng ngôn ngữ đơn giản và biên dịch nó thành bất kỳ ngôn ngữ nào trong số hàng tá ngôn ngữ - bao gồm C và C ++. Tiện ích được viết bằng Java và có thể được bao gồm như là một phần của bản dựng.

Lý do để làm điều này, thay vì mã hóa bằng tay sử dụng mẫu Trạng thái GoF hoặc bất kỳ cách tiếp cận nào khác, là một khi máy trạng thái của bạn được thể hiện dưới dạng mã, cấu trúc cơ bản có xu hướng biến mất dưới trọng lượng của nồi hơi cần được tạo để hỗ trợ nó. Sử dụng phương pháp này mang đến cho bạn một sự tách biệt tuyệt vời giữa các mối quan tâm và bạn giữ cho cấu trúc của máy trạng thái của bạn 'hiển thị'. Mã được tạo tự động đi vào các mô-đun mà bạn không cần phải chạm vào, để bạn có thể quay lại và tìm hiểu cấu trúc của máy trạng thái mà không ảnh hưởng đến mã hỗ trợ mà bạn đã viết.

Xin lỗi, tôi đang quá nhiệt tình và không nghi ngờ gì về việc khiến mọi người bỏ cuộc. Nhưng nó là một tiện ích tốt nhất, và cũng được ghi chép lại.


20

Hãy chắc chắn kiểm tra công việc của Miro Samek (blog State Space , trang web State Machines & Tools ), có bài viết tại Tạp chí người dùng C / C ++ rất tuyệt.

Trang web này có chứa một (C / C ++) thực hiện hoàn toàn trong cả mã nguồn mở và giấy phép thương mại của một khung máy nhà nước (QP Framework) , một xử lý sự kiện (QEP) , một công cụ mô hình cơ bản (QM)truy tìm công cụ (QSpy) mà cho phép vẽ các máy trạng thái, tạo mã và gỡ lỗi chúng.

Cuốn sách chứa một lời giải thích sâu rộng về những gì / tại sao thực hiện và cách sử dụng nó và cũng là tài liệu tuyệt vời để hiểu được các nguyên tắc cơ bản của máy móc nhà nước và hữu hạn.

Trang web cũng chứa các liên kết đến một số gói hỗ trợ bảng để sử dụng phần mềm với các nền tảng nhúng.


Tôi đã sửa đổi tiêu đề của câu hỏi theo cách chơi chữ của bạn.
jldupont

@jldupont: Tôi chỉ có nghĩa là tốt hơn để làm rõ. Tôi đã xóa các phần không liên quan trong câu trả lời của tôi bây giờ.
Daniel Daranas

1
Tôi đã thêm những gì mong đợi trên trang web / sách, khi đã sử dụng phần mềm thành công; đó là cuốn sách tốt nhất trên kệ sách của tôi.
Adriaan

@Adriann, lời giải thích tuyệt vời! Tôi vừa sửa đổi trang chủ của trang web, liên kết trước đó đã ngừng hoạt động.
Daniel Daranas

2
Các liên kết đã chết hoặc trỏ đến trang chủ của trang web dường như đã thay đổi hướng của nó sang phần mềm nhúng. Bạn vẫn có thể thấy một số nội dung trên state-machine.com/resource/articles.php , nhưng thậm chí có hầu hết các liên kết liên quan đến máy trạng thái đã chết. Đây là một trong những liên kết tốt duy nhất ở đó: state-machine.com/resource/ từ
Tatiana Racheva

11

Tôi đã làm một cái gì đó tương tự như những gì paxdiablo mô tả, chỉ thay vì một mảng chuyển trạng thái / sự kiện, tôi thiết lập một mảng 2 chiều của các con trỏ hàm, với giá trị sự kiện là chỉ số của một trục và giá trị trạng thái hiện tại là cai khac. Sau đó, tôi chỉ cần gọi state = state_table[event][state](params)và điều đúng xảy ra. Tất nhiên, các ô đại diện cho các kết hợp trạng thái / sự kiện không hợp lệ có được một con trỏ tới một hàm nói như vậy.

Rõ ràng, điều này chỉ hoạt động nếu các giá trị trạng thái và sự kiện là cả hai phạm vi liền kề và bắt đầu từ 0 hoặc đủ gần.


1
Cảm thấy như giải pháp này không có quy mô độc đáo: điền quá nhiều bảng, không?
jldupont

2
+1. Vấn đề mở rộng ở đây là bộ nhớ - giải pháp của riêng tôi có vấn đề mở rộng thời gian, tức là mất thời gian để quét bảng chuyển tiếp (mặc dù bạn có thể tối ưu hóa thủ công cho các chuyển đổi phổ biến nhất). Điều này hy sinh bộ nhớ cho tốc độ - nó chỉ là một sự đánh đổi. Bạn có thể cần kiểm tra giới hạn nhưng đó không phải là một giải pháp tồi.
paxdiablo

Các bạn - Nhận xét của tôi không được đưa ra như dự định: Tôi có nghĩa là nó tốn nhiều công sức và dễ bị lỗi hơn. Nếu bạn thêm một trạng thái / sự kiện, rất nhiều chỉnh sửa cần phải được thực hiện.
jldupont

3
Không ai nói mảng 2D được khởi tạo bằng tay. Có lẽ có thứ gì đó đọc tệp cấu hình và tạo tệp đó (hoặc ít nhất là chắc chắn có thể có).
John Zwinck

Một cách để khởi tạo các mảng như thế là sử dụng bộ tiền xử lý làm chất kết dính muộn (trái ngược với ràng buộc sớm). Bạn xác định danh sách tất cả các trạng thái #define STATE_LIST() \STATE_LIST_ENTRY(state1)\STATE_LIST_ENTRY(state2)\...(một dòng mới được ngụ ý sau mỗi trạng thái \ ) trong đó bạn (xác định lại) macro mục nhập khi bạn sử dụng macro STATE_LIST. Ví dụ - tạo một mảng các tên trạng thái : #define STATE_LIST_ENTRY(s) #s , \n const char *state_names[] = { STATE_LIST() };\n #undef STATE_LIST_ENTRY. Một số công việc để thiết lập đầu tiên, nhưng điều này là vô cùng mạnh mẽ. Thêm trạng thái mới -> đảm bảo không bỏ lỡ.
hlovdal

9

Một "khung" máy trạng thái C ++ dựa trên mẫu rất đẹp được đưa ra bởi Stefan Heinzmann trong bài viết của mình .

Vì không có liên kết đến tải xuống mã hoàn chỉnh trong bài viết, tôi đã tự do dán mã vào một dự án và kiểm tra nó. Những thứ dưới đây được thử nghiệm và bao gồm một vài phần còn thiếu nhỏ nhưng khá rõ ràng.

Sự đổi mới chính ở đây là trình biên dịch đang tạo mã rất hiệu quả. Hành động nhập / xuất trống không có chi phí. Các hành động nhập / thoát không trống được nội tuyến. Trình biên dịch cũng đang xác minh tính đầy đủ của statechart. Hành động thiếu tạo ra lỗi liên kết. Điều duy nhất không bị bắt là mất tích Top::init.

Đây là một giải pháp thay thế rất hay cho triển khai của Miro Samek, nếu bạn có thể sống mà không thiếu thứ gì - đây là một triển khai UML Statechart hoàn chỉnh, mặc dù nó thực hiện chính xác ngữ nghĩa UML, trong khi mã của Samek theo thiết kế không xử lý thoát / chuyển đổi / hành động nhập theo đúng thứ tự.

Nếu mã này hoạt động cho những gì bạn cần làm và bạn có một trình biên dịch C ++ phù hợp cho hệ thống của mình, nó có thể sẽ hoạt động tốt hơn so với triển khai C / C ++ của Miro. Trình biên dịch tạo ra một triển khai máy trạng thái chuyển tiếp O (1) phẳng cho bạn. Nếu kiểm toán đầu ra lắp ráp xác nhận rằng các tối ưu hóa hoạt động như mong muốn, bạn sẽ tiến gần đến hiệu suất lý thuyết. Phần tốt nhất: đó là mã tương đối nhỏ, dễ hiểu.

#ifndef HSM_HPP
#define HSM_HPP

// This code is from:
// Yet Another Hierarchical State Machine
// by Stefan Heinzmann
// Overload issue 64 december 2004
// http://accu.org/index.php/journals/252

/* This is a basic implementation of UML Statecharts.
 * The key observation is that the machine can only
 * be in a leaf state at any given time. The composite
 * states are only traversed, never final.
 * Only the leaf states are ever instantiated. The composite
 * states are only mechanisms used to generate code. They are
 * never instantiated.
 */

// Helpers

// A gadget from Herb Sutter's GotW #71 -- depends on SFINAE
template<class D, class B>
class IsDerivedFrom {
    class Yes { char a[1]; };
    class No  { char a[10]; };
    static Yes Test(B*); // undefined
    static No Test(...); // undefined
public:
    enum { Res = sizeof(Test(static_cast<D*>(0))) == sizeof(Yes) ? 1 : 0 };
};

template<bool> class Bool {};

// Top State, Composite State and Leaf State

template <typename H>
struct TopState {
    typedef H Host;
    typedef void Base;
    virtual void handler(Host&) const = 0;
    virtual unsigned getId() const = 0;
};

template <typename H, unsigned id, typename B>
struct CompState;

template <typename H, unsigned id, typename B = CompState<H, 0, TopState<H> > >
struct CompState : B {
    typedef B Base;
    typedef CompState<H, id, Base> This;
    template <typename X> void handle(H& h, const X& x) const { Base::handle(h, x); }
    static void init(H&); // no implementation
    static void entry(H&) {}
    static void exit(H&) {}
};

template <typename H>
struct CompState<H, 0, TopState<H> > : TopState<H> {
    typedef TopState<H> Base;
    typedef CompState<H, 0, Base> This;
    template <typename X> void handle(H&, const X&) const {}
    static void init(H&); // no implementation
    static void entry(H&) {}
    static void exit(H&) {}
};

template <typename H, unsigned id, typename B = CompState<H, 0, TopState<H> > >
struct LeafState : B {
    typedef H Host;
    typedef B Base;
    typedef LeafState<H, id, Base> This;
    template <typename X> void handle(H& h, const X& x) const { Base::handle(h, x); }
    virtual void handler(H& h) const { handle(h, *this); }
    virtual unsigned getId() const { return id; }
    static void init(H& h) { h.next(obj); } // don't specialize this
    static void entry(H&) {}
    static void exit(H&) {}
    static const LeafState obj; // only the leaf states have instances
};

template <typename H, unsigned id, typename B>
const LeafState<H, id, B> LeafState<H, id, B>::obj;

// Transition Object

template <typename C, typename S, typename T>
// Current, Source, Target
struct Tran {
    typedef typename C::Host Host;
    typedef typename C::Base CurrentBase;
    typedef typename S::Base SourceBase;
    typedef typename T::Base TargetBase;
    enum { // work out when to terminate template recursion
        eTB_CB = IsDerivedFrom<TargetBase, CurrentBase>::Res,
        eS_CB = IsDerivedFrom<S, CurrentBase>::Res,
        eS_C = IsDerivedFrom<S, C>::Res,
        eC_S = IsDerivedFrom<C, S>::Res,
        exitStop = eTB_CB && eS_C,
        entryStop = eS_C || eS_CB && !eC_S
    };
    // We use overloading to stop recursion.
    // The more natural template specialization
    // method would require to specialize the inner
    // template without specializing the outer one,
    // which is forbidden.
    static void exitActions(Host&, Bool<true>) {}
    static void exitActions(Host&h, Bool<false>) {
        C::exit(h);
        Tran<CurrentBase, S, T>::exitActions(h, Bool<exitStop>());
    }
    static void entryActions(Host&, Bool<true>) {}
    static void entryActions(Host& h, Bool<false>) {
        Tran<CurrentBase, S, T>::entryActions(h, Bool<entryStop>());
        C::entry(h);
    }
    Tran(Host & h) : host_(h) {
        exitActions(host_, Bool<false>());
    }
    ~Tran() {
        Tran<T, S, T>::entryActions(host_, Bool<false>());
        T::init(host_);
    }
    Host& host_;
};

// Initializer for Compound States

template <typename T>
struct Init {
    typedef typename T::Host Host;
    Init(Host& h) : host_(h) {}
    ~Init() {
        T::entry(host_);
        T::init(host_);
    }
    Host& host_;
};

#endif // HSM_HPP

Mã kiểm tra sau.

#include <cstdio>
#include "hsm.hpp"
#include "hsmtest.hpp"

/* Implements the following state machine from Miro Samek's
 * Practical Statecharts in C/C++
 *
 * |-init-----------------------------------------------------|
 * |                           s0                             |
 * |----------------------------------------------------------|
 * |                                                          |
 * |    |-init-----------|        |-------------------------| |
 * |    |       s1       |---c--->|            s2           | |
 * |    |----------------|<--c----|-------------------------| |
 * |    |                |        |                         | |
 * |<-d-| |-init-------| |        | |-init----------------| | |
 * |    | |     s11    |<----f----| |          s21        | | |
 * | /--| |------------| |        | |---------------------| | |
 * | a  | |            | |        | |                     | | |
 * | \->| |            |------g--------->|-init------|    | | |
 * |    | |____________| |        | |-b->|    s211   |---g--->|
 * |    |----b---^       |------f------->|           |    | | |
 * |    |________________|        | |<-d-|___________|<--e----|
 * |                              | |_____________________| | |
 * |                              |_________________________| |
 * |__________________________________________________________|
 */

class TestHSM;

typedef CompState<TestHSM,0>     Top;
typedef CompState<TestHSM,1,Top>   S0;
typedef CompState<TestHSM,2,S0>      S1;
typedef LeafState<TestHSM,3,S1>        S11;
typedef CompState<TestHSM,4,S0>      S2;
typedef CompState<TestHSM,5,S2>        S21;
typedef LeafState<TestHSM,6,S21>         S211;

enum Signal { A_SIG, B_SIG, C_SIG, D_SIG, E_SIG, F_SIG, G_SIG, H_SIG };

class TestHSM {
public:
    TestHSM() { Top::init(*this); }
    ~TestHSM() {}
    void next(const TopState<TestHSM>& state) {
        state_ = &state;
    }
    Signal getSig() const { return sig_; }
    void dispatch(Signal sig) {
        sig_ = sig;
        state_->handler(*this);
    }
    void foo(int i) {
        foo_ = i;
    }
    int foo() const {
        return foo_;
    }
private:
    const TopState<TestHSM>* state_;
    Signal sig_;
    int foo_;
};

bool testDispatch(char c) {
    static TestHSM test;
    if (c<'a' || 'h'<c) {
        return false;
    }
    printf("Signal<-%c", c);
    test.dispatch((Signal)(c-'a'));
    printf("\n");
    return true;
}

int main(int, char**) {
    testDispatch('a');
    testDispatch('e');
    testDispatch('e');
    testDispatch('a');
    testDispatch('h');
    testDispatch('h');
    return 0;
}

#define HSMHANDLER(State) \
    template<> template<typename X> inline void State::handle(TestHSM& h, const X& x) const

HSMHANDLER(S0) {
    switch (h.getSig()) {
    case E_SIG: { Tran<X, This, S211> t(h);
        printf("s0-E;");
        return; }
    default:
        break;
    }
    return Base::handle(h, x);
}

HSMHANDLER(S1) {
    switch (h.getSig()) {
    case A_SIG: { Tran<X, This, S1> t(h);
        printf("s1-A;"); return; }
    case B_SIG: { Tran<X, This, S11> t(h);
        printf("s1-B;"); return; }
    case C_SIG: { Tran<X, This, S2> t(h);
        printf("s1-C;"); return; }
    case D_SIG: { Tran<X, This, S0> t(h);
        printf("s1-D;"); return; }
    case F_SIG: { Tran<X, This, S211> t(h);
        printf("s1-F;"); return; }
    default: break;
    }
    return Base::handle(h, x);
}

HSMHANDLER(S11) {
    switch (h.getSig()) {
    case G_SIG: { Tran<X, This, S211> t(h);
        printf("s11-G;"); return; }
    case H_SIG: if (h.foo()) {
            printf("s11-H");
            h.foo(0); return;
        } break;
    default: break;
    }
    return Base::handle(h, x);
}

HSMHANDLER(S2) {
    switch (h.getSig()) {
    case C_SIG: { Tran<X, This, S1> t(h);
        printf("s2-C"); return; }
    case F_SIG: { Tran<X, This, S11> t(h);
        printf("s2-F"); return; }
    default: break;
    }
    return Base::handle(h, x);
}

HSMHANDLER(S21) {
    switch (h.getSig()) {
    case B_SIG: { Tran<X, This, S211> t(h);
        printf("s21-B;"); return; }
    case H_SIG: if (!h.foo()) {
            Tran<X, This, S21> t(h);
            printf("s21-H;"); h.foo(1);
            return;
        } break;
    default: break;
    }
    return Base::handle(h, x);
}

HSMHANDLER(S211) {
    switch (h.getSig()) {
    case D_SIG: { Tran<X, This, S21> t(h);
        printf("s211-D;"); return; }
    case G_SIG: { Tran<X, This, S0> t(h);
        printf("s211-G;"); return; }
    }
    return Base::handle(h, x);
}

#define HSMENTRY(State) \
    template<> inline void State::entry(TestHSM&) { \
        printf(#State "-ENTRY;"); \
    }

HSMENTRY(S0)
HSMENTRY(S1)
HSMENTRY(S11)
HSMENTRY(S2)
HSMENTRY(S21)
HSMENTRY(S211)

#define HSMEXIT(State) \
    template<> inline void State::exit(TestHSM&) { \
        printf(#State "-EXIT;"); \
    }

HSMEXIT(S0)
HSMEXIT(S1)
HSMEXIT(S11)
HSMEXIT(S2)
HSMEXIT(S21)
HSMEXIT(S211)

#define HSMINIT(State, InitState) \
    template<> inline void State::init(TestHSM& h) { \
       Init<InitState> i(h); \
       printf(#State "-INIT;"); \
    }

HSMINIT(Top, S0)
HSMINIT(S0, S1)
HSMINIT(S1, S11)
HSMINIT(S2, S21)
HSMINIT(S21, S211)

Hmm ... sth bị thiếu trong mã của bạn. Trước hết bạn bao gồm hai tiêu đề, nhưng chỉ cung cấp cái đầu tiên. Khi tôi chỉ nhận xét câu lệnh "bao gồm", tôi gặp lỗi này khi biên dịch: d: \ 1 \ hsm> g ++ test.cpp test.cpp: 195: 1: error: chuyên môn hóa 'static void CompState <H, id, B> :: init (H &) [với H = TestHSM; unsign int id = 0u; B = CompState <TestHSM, 0u, TopState <TestHSM >>] 'sau khi khởi tạo
Freddie Chopin

Tôi đã phải di chuyển các định nghĩa của tất cả HSMINIT () lên trên lớp TestHSM và nó biên dịch và hoạt động tốt (; Điều duy nhất sai là thực tế là tất cả các chuyển đổi là "bên ngoài", trong khi chúng phải là "bên trong" - đã có một số tranh luận về nó trong bài báo và tác giả đã quyết định rằng "extrenal" là đúng, nhưng các mũi tên được sử dụng gợi ý "nội bộ".
Freddie Chopin

5

Kỹ thuật tôi thích cho các máy trạng thái (ít nhất là các điều khiển chương trình) là sử dụng các con trỏ hàm. Mỗi trạng thái được đại diện bởi một chức năng khác nhau. Hàm lấy ký hiệu đầu vào và trả về con trỏ hàm cho trạng thái tiếp theo. Các màn hình vòng lặp công văn trung tâm nhận đầu vào tiếp theo, đưa nó về trạng thái hiện tại và xử lý kết quả.

Việc gõ vào nó có một chút kỳ lạ, vì C không có cách nào để chỉ ra các loại con trỏ hàm tự trả về, do đó các hàm trạng thái trả về void*. Nhưng bạn có thể làm một cái gì đó như thế này:

typedef void* (*state_handler)(input_symbol_t);
void dispatch_fsm()
{
    state_handler current = initial_handler;
    /* Let's assume returning null indicates end-of-machine */
    while (current) {
        current = current(get_input);
    }
 }

Sau đó, các hàm trạng thái riêng lẻ của bạn có thể bật đầu vào của chúng để xử lý và trả về giá trị phù hợp.


+1 điều đó thực sự tốt và cung cấp các vị trí đẹp để thực hiện chức năng bên trong các chức năng chuyển tiếp
Fire Crow

5

Trường hợp đơn giản nhất

enum event_type { ET_THIS, ET_THAT };
union event_parm { uint8_t this; uint16_t that; }
static void handle_event(enum event_type event, union event_parm parm)
{
  static enum { THIS, THAT } state;
  switch (state)
  {
    case THIS:
    switch (event)
    {
      case ET_THIS:
      // Handle event.
      break;

      default:
      // Unhandled events in this state.
      break;
    }
    break;

    case THAT:
    // Handle state.
    break;
  }
}

Điểm: Trạng thái là riêng tư, không chỉ cho đơn vị biên dịch mà còn cho event_handler. Các trường hợp đặc biệt có thể được xử lý tách biệt với công tắc chính bằng cách sử dụng bất kỳ cấu trúc nào được coi là cần thiết.

Trường hợp phức tạp hơn

Khi công tắc trở nên lớn hơn một vài màn hình, hãy chia nó thành các chức năng xử lý từng trạng thái, sử dụng bảng trạng thái để tra cứu trực tiếp chức năng. Nhà nước vẫn là riêng tư để xử lý sự kiện. Các hàm xử lý trạng thái trả về trạng thái tiếp theo. Nếu cần một số sự kiện vẫn có thể được điều trị đặc biệt trong xử lý sự kiện chính. Tôi thích đưa vào các sự kiện giả cho mục nhập và thoát trạng thái và có lẽ máy trạng thái bắt đầu:

enum state_type { THIS, THAT, FOO, NA };
enum event_type { ET_START, ET_ENTER, ET_EXIT, ET_THIS, ET_THAT, ET_WHATEVER, ET_TIMEOUT };
union event_parm { uint8_t this; uint16_t that; };
static void handle_event(enum event_type event, union event_parm parm)
{
  static enum state_type state;
  static void (* const state_handler[])(enum event_type event, union event_parm parm) = { handle_this, handle_that };
  enum state_type next_state = state_handler[state](event, parm);
  if (NA != next_state && state != next_state)
  {
    (void)state_handler[state](ET_EXIT, 0);
    state = next_state;
    (void)state_handler[state](ET_ENTER, 0);
  }
}

Tôi không chắc chắn nếu tôi đóng đinh cú pháp, đặc biệt là liên quan đến mảng con trỏ hàm. Tôi đã không chạy bất kỳ thứ này thông qua một trình biên dịch. Khi xem xét, tôi nhận thấy rằng tôi đã quên loại bỏ rõ ràng trạng thái tiếp theo khi xử lý các sự kiện giả (dấu ngoặc (void) trước lệnh gọi tới state_handler ()). Đây là điều mà tôi muốn làm ngay cả khi trình biên dịch chấp nhận bỏ qua âm thầm. Nó nói với độc giả của mã rằng "vâng, tôi thực sự có nghĩa là gọi hàm mà không sử dụng giá trị trả về" và nó có thể ngăn các công cụ phân tích tĩnh cảnh báo về nó. Nó có thể là bình dị bởi vì tôi không nhớ đã thấy ai khác làm điều này.

Điểm: thêm một chút phức tạp (kiểm tra xem trạng thái tiếp theo có khác với hiện tại không), có thể tránh được mã trùng lặp ở nơi khác, bởi vì các hàm xử lý trạng thái có thể thưởng thức các sự kiện giả xảy ra khi trạng thái được nhập và rời. Hãy nhớ rằng trạng thái không thể thay đổi khi xử lý các sự kiện giả, vì kết quả của trình xử lý trạng thái bị loại bỏ sau các sự kiện này. Tất nhiên bạn có thể chọn để sửa đổi hành vi.

Một trình xử lý nhà nước sẽ trông như vậy:

static enum state_type handle_this(enum event_type event, union event_parm parm)
{
  enum state_type next_state = NA;
  switch (event)
  {
    case ET_ENTER:
    // Start a timer to do whatever.
    // Do other stuff necessary when entering this state.
    break;

    case ET_WHATEVER:
    // Switch state.
    next_state = THAT;
    break;

    case ET_TIMEOUT:
    // Switch state.
    next_state = FOO;
    break;

    case ET_EXIT:
    // Stop the timer.
    // Generally clean up this state.
    break;
  }
  return next_state;
}

Phức tạp hơn

Khi đơn vị biên dịch trở nên quá lớn (bất kể bạn cảm thấy thế nào, tôi nên nói khoảng 1000 dòng), đặt mỗi trình xử lý trạng thái vào một tệp riêng. Khi mỗi trình xử lý trạng thái trở nên dài hơn một vài màn hình, hãy phân tách từng sự kiện trong một chức năng riêng biệt, tương tự như cách phân chia công tắc trạng thái. Bạn có thể làm điều này theo một số cách, tách biệt với tiểu bang hoặc bằng cách sử dụng một bảng chung hoặc kết hợp các sơ đồ khác nhau. Một số trong số họ đã được bảo hiểm ở đây bởi những người khác. Sắp xếp các bảng của bạn và sử dụng tìm kiếm nhị phân nếu tốc độ là một yêu cầu.

Lập trình chung

Tôi muốn bộ xử lý trước xử lý các vấn đề như sắp xếp bảng hoặc thậm chí tạo máy trạng thái từ mô tả, cho phép bạn "viết chương trình về chương trình". Tôi tin rằng đây là những gì người Boost đang khai thác các mẫu C ++, nhưng tôi thấy cú pháp khó hiểu.

Bảng hai chiều

Tôi đã sử dụng các bảng trạng thái / sự kiện trong quá khứ nhưng tôi phải nói rằng đối với những trường hợp đơn giản nhất tôi không thấy chúng cần thiết và tôi thích sự rõ ràng và dễ đọc của câu lệnh chuyển đổi ngay cả khi nó mở rộng ra toàn màn hình. Đối với các trường hợp phức tạp hơn, các bảng nhanh chóng thoát khỏi tầm tay như những người khác đã lưu ý. Các thành ngữ tôi trình bày ở đây cho phép bạn thêm một loạt các sự kiện và trạng thái khi bạn cảm thấy thích nó, mà không phải duy trì bảng tiêu thụ bộ nhớ (ngay cả khi đó có thể là bộ nhớ chương trình).

Khước từ

Nhu cầu đặc biệt có thể khiến những thành ngữ này ít hữu ích hơn, nhưng tôi đã thấy chúng rất rõ ràng và có thể duy trì được.


Tôi sẽ tránh 'đây' là một tên biến hoặc biểu tượng chỉ dành cho liên kết, ngay cả khi nó không thực sự là một từ dành riêng.
XtL

4

Vô cùng chưa được kiểm tra, nhưng thú vị để viết mã, bây giờ trong một phiên bản tinh tế hơn câu trả lời ban đầu của tôi; phiên bản cập nhật có thể được tìm thấy tại mercurial.intuxication.org :

sm.h

#ifndef SM_ARGS
#error "SM_ARGS undefined: " \
    "use '#define SM_ARGS (void)' to get an empty argument list"
#endif

#ifndef SM_STATES
#error "SM_STATES undefined: " \
    "you must provide a list of comma-separated states"
#endif

typedef void (*sm_state) SM_ARGS;
static const sm_state SM_STATES;

#define sm_transit(STATE) ((sm_state (*) SM_ARGS)STATE)

#define sm_def(NAME) \
    static sm_state NAME ## _fn SM_ARGS; \
    static const sm_state NAME = (sm_state)NAME ## _fn; \
    static sm_state NAME ## _fn SM_ARGS

ví dụ

#include <stdio.h>

#define SM_ARGS (int i)
#define SM_STATES EVEN, ODD
#include "sm.h"

sm_def(EVEN)
{
    printf("even %i\n", i);
    return ODD;
}

sm_def(ODD)
{
    printf("odd  %i\n", i);
    return EVEN;
}

int main(void)
{
    int i = 0;
    sm_state state = EVEN;

    for(; i < 10; ++i)
        state = sm_transit(state)(i);

    return 0;
}

14
Tôi thích bình luận "cực kỳ chưa được kiểm tra". Có vẻ như chỉ ra rằng có nhiều mức độ chưa được kiểm chứng và bạn đã bỏ ra khá nhiều nỗ lực để không kiểm tra nó :-)
paxdiablo

@Christoph liên kết trong câu trả lời này bị hỏng. Ngoài ra, bạn đã kiểm tra mã này hay chưa? Nếu nó đã được thử nghiệm và hoạt động, bạn nên loại bỏ nó khỏi câu trả lời. Cũng có thể hiển thị một ví dụ về mã nào dẫn đến kết quả này khi các macro đã được mở rộng. Tôi thích ý tưởng chung.
Joakim

4

Tôi thực sự thích câu trả lời của paxdiable và quyết định triển khai tất cả các tính năng còn thiếu cho ứng dụng của tôi như các biến bảo vệ và dữ liệu cụ thể của máy trạng thái.

Tôi đã tải lên triển khai của mình lên trang web này để chia sẻ với cộng đồng. Nó đã được thử nghiệm bằng cách sử dụng IAR Embedded Workbench cho ARM.

https://sourceforge.net/projects/compactfsm/


Tìm kiếm điều này vào năm 2018 và nó vẫn được áp dụng. Tôi đã đọc câu trả lời @paxdiablo và tôi đã sử dụng thành công kiểu triển khai đó trong các hệ thống nhúng trước đây. Giải pháp này bổ sung những điều còn thiếu từ câu trả lời của paxdiablos :)
Kristoffer

4

Một công cụ mã nguồn mở thú vị khác là Yakindu Statechart Tools trên statecharts.org . Nó sử dụng các statecharts của Harel và do đó cung cấp các trạng thái phân cấp và song song và tạo mã C và C ++ (cũng như Java). Nó không sử dụng các thư viện nhưng theo cách tiếp cận 'mã đơn giản'. Mã về cơ bản áp dụng các cấu trúc trường hợp chuyển đổi. Các trình tạo mã cũng có thể được tùy chỉnh. Ngoài ra, công cụ cung cấp nhiều tính năng khác.


3

Đến muộn (như thường lệ) nhưng quét các câu trả lời cho đến nay tôi nghĩ thiếu một cái gì đó quan trọng;

Tôi đã tìm thấy trong các dự án của riêng mình rằng nó có thể rất hữu ích khi không có chức năng cho mọi kết hợp trạng thái / sự kiện hợp lệ. Tôi thích ý tưởng về việc có một bảng trạng thái / sự kiện 2D một cách hiệu quả. Nhưng tôi thích các phần tử bảng hơn là một con trỏ hàm đơn giản. Thay vào đó, tôi cố gắng tổ chức thiết kế của mình, vì vậy, nó bao gồm một loạt các yếu tố hoặc hành động nguyên tử đơn giản. Bằng cách đó tôi có thể liệt kê các yếu tố nguyên tử đơn giản đó tại mỗi giao điểm của bảng trạng thái / sự kiện. Ý tưởng là bạn không phải xác định một khối lượng N bình phương (thường rất đơn giản). Tại sao có một cái gì đó rất dễ bị lỗi, tốn thời gian, khó viết, khó đọc, bạn đặt tên cho nó?

Tôi cũng bao gồm một trạng thái mới tùy chọn và một con trỏ hàm tùy chọn cho mỗi ô trong bảng. Con trỏ hàm có sẵn cho những trường hợp đặc biệt mà bạn không muốn bắn ra một danh sách các hành động nguyên tử.

Bạn biết bạn đang làm đúng khi bạn có thể thể hiện rất nhiều chức năng khác nhau, chỉ bằng cách chỉnh sửa bảng của bạn, không có mã mới để viết.


2
Có lẽ một ví dụ sẽ tốt đẹp, phải không?
jldupont

1
Một ví dụ thực tế có thể được trình bày một cách cô lập là một nhiệm vụ đầy thách thức đòi hỏi nhiều thời gian hơn tôi chuẩn bị đưa ra ngay lúc này. Có điều gì trong bài viết của tôi đặc biệt khó hiểu không? Có lẽ tôi có thể diễn đạt nó rõ ràng hơn. Ý tưởng rất đơn giản; Không xác định cơ chế trạng thái yêu cầu một chức năng riêng cho mọi kết hợp sự kiện / trạng thái, bạn có quá nhiều chức năng theo cách đó. Thay vào đó, hãy tìm một cách khác để mô tả chức năng bạn muốn cho sự kết hợp sự kiện / trạng thái đó, ít nhất là trong phần lớn các trường hợp.
Bill Forster

2
Hiểu: một ví dụ mã giả sẽ là tốt nhưng quan điểm của bạn là rõ ràng.
jldupont

3

Alrght, tôi nghĩ rằng tôi chỉ khác một chút so với mọi người. Một chút tách mã và dữ liệu hơn tôi thấy trong các câu trả lời khác. Tôi thực sự đọc lên lý thuyết để viết cái này, trong đó thực hiện một ngôn ngữ thông thường đầy đủ (không có biểu thức chính quy, thật đáng buồn). Ullman, Minsky, Chomsky. Không thể nói tôi hiểu tất cả, nhưng tôi đã rút ra từ những bậc thầy cũ một cách trực tiếp nhất có thể: thông qua lời nói của họ.

Tôi sử dụng một con trỏ hàm đến một vị từ xác định việc chuyển sang trạng thái 'có' hoặc trạng thái 'không'. Điều này tạo điều kiện cho việc tạo ra một người chấp nhận trạng thái hữu hạn cho một ngôn ngữ thông thường mà bạn lập trình theo cách giống như ngôn ngữ lắp ráp hơn. Xin đừng bỏ qua những lựa chọn tên ngớ ngẩn của tôi. 'czek' == 'kiểm tra'. 'Grok' == [hãy tìm nó trong Từ điển Hacker].

Vì vậy, với mỗi lần lặp, czek gọi một hàm vị ngữ với ký tự hiện tại là đối số. Nếu vị ngữ trả về true, ký tự được tiêu thụ (con trỏ nâng cao) và chúng tôi thực hiện theo chuyển đổi 'y' để chọn trạng thái tiếp theo. Nếu vị ngữ trả về sai, ký tự KHÔNG được sử dụng và chúng ta thực hiện theo chuyển đổi 'n'. Vì vậy, mỗi hướng dẫn là một nhánh hai chiều! Tôi phải đọc The Story of Mel vào thời điểm đó.

Mã này xuất phát trực tiếp từ trình thông dịch postcript của tôi và phát triển thành dạng hiện tại với nhiều hướng dẫn từ các nghiên cứu sinh trên comp.lang.c. Vì postcript về cơ bản không có cú pháp (chỉ yêu cầu dấu ngoặc cân bằng), nên một Ngôn ngữ thông thường cũng thích chức năng này như là trình phân tích cú pháp.

/* currentstr is set to the start of string by czek
   and used by setrad (called by israd) to set currentrad
   which is used by israddig to determine if the character
   in question is valid for the specified radix
   --
   a little semantic checking in the syntax!
 */
char *currentstr;
int currentrad;
void setrad(void) {
    char *end;
    currentrad = strtol(currentstr, &end, 10);
    if (*end != '#' /* just a sanity check,
                       the automaton should already have determined this */
    ||  currentrad > 36
    ||  currentrad < 2)
        fatal("bad radix"); /* should probably be a simple syntaxerror */
}

/*
   character classes
   used as tests by automatons under control of czek
 */
char *alpha = "0123456789" "ABCDE" "FGHIJ" "KLMNO" "PQRST" "UVWXYZ";
#define EQ(a,b) a==b
#define WITHIN(a,b) strchr(a,b)!=NULL
int israd  (int c) {
    if (EQ('#',c)) { setrad(); return true; }
    return false;
}
int israddig(int c) {
    return strchrnul(alpha,toupper(c))-alpha <= currentrad;
}
int isdot  (int c) {return EQ('.',c);}
int ise    (int c) {return WITHIN("eE",c);}
int issign (int c) {return WITHIN("+-",c);}
int isdel  (int c) {return WITHIN("()<>[]{}/%",c);}
int isreg  (int c) {return c!=EOF && !isspace(c) && !isdel(c);}
#undef WITHIN
#undef EQ

/*
   the automaton type
 */
typedef struct { int (*pred)(int); int y, n; } test;

/*
   automaton to match a simple decimal number
 */
/* /^[+-]?[0-9]+$/ */
test fsm_dec[] = {
/* 0*/ { issign,  1,  1 },
/* 1*/ { isdigit, 2, -1 },
/* 2*/ { isdigit, 2, -1 },
};
int acc_dec(int i) { return i==2; }

/*
   automaton to match a radix number
 */
/* /^[0-9]+[#][a-Z0-9]+$/ */
test fsm_rad[] = {
/* 0*/ { isdigit,  1, -1 },
/* 1*/ { isdigit,  1,  2 },
/* 2*/ { israd,    3, -1 },
/* 3*/ { israddig, 4, -1 },
/* 4*/ { israddig, 4, -1 },
};
int acc_rad(int i) { return i==4; }

/*
   automaton to match a real number
 */
/* /^[+-]?(d+(.d*)?)|(d*.d+)([eE][+-]?d+)?$/ */
/* represents the merge of these (simpler) expressions
   [+-]?[0-9]+\.[0-9]*([eE][+-]?[0-9]+)?
   [+-]?[0-9]*\.[0-9]+([eE][+-]?[0-9]+)?
   The complexity comes from ensuring at least one
   digit in the integer or the fraction with optional
   sign and optional optionally-signed exponent.
   So passing isdot in state 3 means at least one integer digit has been found
   but passing isdot in state 4 means we must find at least one fraction digit
   via state 5 or the whole thing is a bust.
 */
test fsm_real[] = {
/* 0*/ { issign,  1,   1 },
/* 1*/ { isdigit, 2,   4 },
/* 2*/ { isdigit, 2,   3 },
/* 3*/ { isdot,   6,   7 },
/* 4*/ { isdot,   5,  -1 },
/* 5*/ { isdigit, 6,  -1 },
/* 6*/ { isdigit, 6,   7 },
/* 7*/ { ise,     8,  -1 },
/* 8*/ { issign,  9,   9 },
/* 9*/ { isdigit, 10, -1 },
/*10*/ { isdigit, 10, -1 },
};
int acc_real(int i) {
    switch(i) {
        case 2: /* integer */
        case 6: /* real */
        case 10: /* real with exponent */
            return true;
    }
    return false;
}

/*
   Helper function for grok.
   Execute automaton against the buffer,
   applying test to each character:
       on success, consume character and follow 'y' transition.
       on failure, do not consume but follow 'n' transition.
   Call yes function to determine if the ending state
   is considered an acceptable final state.
   A transition to -1 represents rejection by the automaton
 */
int czek (char *s, test *fsm, int (*yes)(int)) {
    int sta = 0;
    currentstr = s;
    while (sta!=-1 && *s) {
        if (fsm[sta].pred((int)*s)) {
            sta=fsm[sta].y;
            s++;
        } else {
            sta=fsm[sta].n;
        }
    }
    return yes(sta);
}

/*
   Helper function for toke.
   Interpret the contents of the buffer,
   trying automatons to match number formats;
   and falling through to a switch for special characters.
   Any token consisting of all regular characters
   that cannot be interpreted as a number is an executable name
 */
object grok (state *st, char *s, int ns,
    object *src,
    int (*next)(state *,object *),
    void (*back)(state *,int, object *)) {

    if (czek(s, fsm_dec, acc_dec)) {
        long num;
        num = strtol(s,NULL,10);
        if ((num==LONG_MAX || num==LONG_MIN) && errno==ERANGE) {
            error(st,limitcheck);
/*       } else if (num > INT_MAX || num < INT_MIN) { */
/*           error(limitcheck, OP_token); */
        } else {
            return consint(num);
        }
    }

    else if (czek(s, fsm_rad, acc_rad)) {
        long ra,num;
        ra = (int)strtol(s,NULL,10);
        if (ra > 36 || ra < 2) {
            error(st,limitcheck);
        }
        num = strtol(strchr(s,'#')+1, NULL, (int)ra);
        if ((num==LONG_MAX || num==LONG_MIN) && errno==ERANGE) {
            error(st,limitcheck);
/*       } else if (num > INT_MAX || num < INT_MAX) { */
/*           error(limitcheck, OP_token); */
        } else {
            return consint(num);
        }
    }

    else if (czek(s, fsm_real, acc_real)) {
        double num;
        num = strtod(s,NULL);
        if ((num==HUGE_VAL || num==-HUGE_VAL) && errno==ERANGE) {
            error(st,limitcheck);
        } else {
            return consreal(num);
        }
    }

    else switch(*s) {
        case '(': {
            int c, defer=1;
            char *sp = s;

            while (defer && (c=next(st,src)) != EOF ) {
                switch(c) {
                    case '(': defer++; break;
                    case ')': defer--;
                        if (!defer) goto endstring;
                        break;
                    case '\\': c=next(st,src);
                        switch(c) {
                            case '\n': continue;
                            case 'a': c = '\a'; break;
                            case 'b': c = '\b'; break;
                            case 'f': c = '\f'; break;
                            case 'n': c = '\n'; break;
                            case 'r': c = '\r'; break;
                            case 't': c = '\t'; break;
                            case 'v': c = '\v'; break;
                            case '\'': case '\"':
                            case '(': case ')':
                            default: break;
                        }
                }
                if (sp-s>ns) error(st,limitcheck);
                else *sp++ = c;
            }
endstring:  *sp=0;
            return cvlit(consstring(st,s,sp-s));
        }

        case '<': {
            int c;
            char d, *x = "0123456789abcdef", *sp = s;
            while (c=next(st,src), c!='>' && c!=EOF) {
                if (isspace(c)) continue;
                if (isxdigit(c)) c = strchr(x,tolower(c)) - x;
                else error(st,syntaxerror);
                d = (char)c << 4;
                while (isspace(c=next(st,src))) /*loop*/;
                if (isxdigit(c)) c = strchr(x,tolower(c)) - x;
                else error(st,syntaxerror);
                d |= (char)c;
                if (sp-s>ns) error(st,limitcheck);
                *sp++ = d;
            }
            *sp = 0;
            return cvlit(consstring(st,s,sp-s));
        }

        case '{': {
            object *a;
            size_t na = 100;
            size_t i;
            object proc;
            object fin;

            fin = consname(st,"}");
            (a = malloc(na * sizeof(object))) || (fatal("failure to malloc"),0);
            for (i=0 ; objcmp(st,a[i]=toke(st,src,next,back),fin) != 0; i++) {
                if (i == na-1)
                (a = realloc(a, (na+=100) * sizeof(object))) || (fatal("failure to malloc"),0);
            }
            proc = consarray(st,i);
            { size_t j;
                for (j=0; j<i; j++) {
                    a_put(st, proc, j, a[j]);
                }
            }
            free(a);
            return proc;
        }

        case '/': {
            s[1] = (char)next(st,src);
            puff(st, s+2, ns-2, src, next, back);
            if (s[1] == '/') {
                push(consname(st,s+2));
                opexec(st, op_cuts.load);
                return pop();
            }
            return cvlit(consname(st,s+1));
        }

        default: return consname(st,s);
    }
    return null; /* should be unreachable */
}

/*
   Helper function for toke.
   Read into buffer any regular characters.
   If we read one too many characters, put it back
   unless it's whitespace.
 */
int puff (state *st, char *buf, int nbuf,
    object *src,
    int (*next)(state *,object *),
    void (*back)(state *,int, object *)) {
    int c;
    char *s = buf;
    while (isreg(c=next(st,src))) {
        if (s-buf >= nbuf-1) return false;
        *s++ = c;
    }
    *s = 0;
    if (!isspace(c) && c != EOF) back(st,c,src); /* eat interstice */
    return true;
}

/*
   Helper function for Stoken Ftoken.
   Read a token from src using next and back.
   Loop until having read a bona-fide non-whitespace non-comment character.
   Call puff to read into buffer up to next delimiter or space.
   Call grok to figure out what it is.
 */
#define NBUF MAXLINE
object toke (state *st, object *src,
        int (*next)(state *, object *),
        void (*back)(state *, int, object *)) {
    char buf[NBUF] = "", *s=buf;
    int c,sta = 1;
    object o;

    do {
        c=next(st,src);
        //if (c==EOF) return null;
        if (c=='%') {
            if (DUMPCOMMENTS) fputc(c, stdout);
            do {
                c=next(st,src);
                if (DUMPCOMMENTS) fputc(c, stdout);
            } while (c!='\n' && c!='\f' && c!=EOF);
        }
    } while (c!=EOF && isspace(c));
    if (c==EOF) return null;
    *s++ = c;
    *s = 0;
    if (!isdel(c)) sta=puff(st, s,NBUF-1,src,next,back);

    if (sta) {
        o=grok(st,buf,NBUF-1,src,next,back);
        return o;
    } else {
        return null;
    }
}

2
Đây là những gì mà bất kỳ trình phân tích cú pháp hoặc trình tạo lexer nào sẽ sẵn sàng phát ra cho bạn. Vô duyên như vậy. Cho dù bạn muốn mã hóa nó bằng tay là nghi vấn. Nó có giá trị sư phạm, tất nhiên.
Phục hồi Monica

3

boost.org đi kèm với 2 triển khai biểu đồ trạng thái khác nhau:

Như mọi khi, boost sẽ chiếu bạn vào địa ngục mẫu.

Thư viện đầu tiên dành cho các máy trạng thái quan trọng hơn về hiệu năng. Thư viện thứ hai cung cấp cho bạn đường dẫn chuyển tiếp trực tiếp từ UML Statechart sang mã.

Đây là câu hỏi SO yêu cầu so sánh giữa hai nơi mà cả hai tác giả trả lời.



2

Thấy cái này ở đâu đó

#define FSM
#define STATE(x)      s_##x :
#define NEXTSTATE(x)  goto s_##x

FSM {
  STATE(x) {
    ...
    NEXTSTATE(y);
  }

  STATE(y) {
    ...
    if (x == 0)
      NEXTSTATE(y);
    else
      NEXTSTATE(x);
  }
}

1
Điều này thật thú vị, nhưng không có upvote cho đến khi bạn đưa ra một hoặc hai ví dụ (và có lẽ là kết quả không vĩ mô) hoặc một số cuộc thảo luận về lý do tại sao điều này có thể thực tế hơn so với cái khác. Thú vị của việc sử dụng dấu ngoặc và macro mồ côi. Tôi tưởng tượng một cái gì đó tương tự có thể được thực hiện trên một ngôn ngữ thực hiện một số loại tối ưu hóa đệ quy đuôi; bạn có thể sử dụng các lệnh gọi hàm thẳng lên và không lo lắng về việc quá tải không gian ngăn xếp với rác gọi hàm (mà tôi nghĩ là các macro về cơ bản đang khắc phục ở đây)
Ape-inago

2
Ưu điểm của phương pháp này là ...? Tôi thấy một số nhược điểm, chẳng hạn như macro che khuất và việc sử dụng gotođiều đó tạo ra sự phụ thuộc vào hệ điều hành đa nhiệm tiền định.
Craig McQueen

2

Cho rằng bạn ngụ ý rằng bạn có thể sử dụng mã C ++ và do đó là mã OO, tôi sẽ đề nghị đánh giá mẫu 'GoF'state (GoF = Gang of Four, những người đã viết cuốn sách mẫu thiết kế đưa các mẫu thiết kế vào ánh đèn sân khấu).

Nó không đặc biệt phức tạp và nó được sử dụng và thảo luận rộng rãi để dễ dàng nhìn thấy các ví dụ và giải thích trực tuyến.

Bất kỳ ai khác cũng có thể nhận ra mã của bạn vào một ngày sau đó.

Nếu hiệu quả là điều đáng lo ngại, thì thực sự đáng để đánh giá để đảm bảo rằng cách tiếp cận không OO hiệu quả hơn vì nhiều yếu tố ảnh hưởng đến hiệu suất và không phải lúc nào cũng đơn giản là OO xấu, mã chức năng tốt. Tương tự, nếu việc sử dụng bộ nhớ là một hạn chế đối với bạn, một lần nữa đáng thực hiện một số thử nghiệm hoặc tính toán để xem liệu điều này có thực sự là vấn đề đối với ứng dụng cụ thể của bạn hay không nếu bạn sử dụng mẫu trạng thái.

Sau đây là một số liên kết đến mẫu trạng thái 'Gof', như Craig gợi ý:


trông giống như một bình luận: tôi có thể đề nghị bạn đối xử với nó như vậy không? tức là không đặt nó trong phần "trả lời".
jldupont

Sẽ tốt hơn nếu bạn có thể cung cấp một liên kết URL tốt cho "mẫu trạng thái GoF", cho những người không quen thuộc với nó.
Craig McQueen

1
@jldupont - Nhận xét công bằng. Tôi đã thay đổi văn bản để biến nó thành một câu trả lời phù hợp theo cảm nhận của tôi dựa trên kinh nghiệm cá nhân, trừ khi có vấn đề về hiệu suất cụ thể, phương pháp GoF hoạt động tốt và sẽ có một 'cơ sở người dùng' tương đối lớn
Mick

@Craig - thêm một số liên kết. Cả hai đều trông chính xác và rõ ràng tại thời điểm tôi thêm chúng.
Mick

2

Dưới đây là một ví dụ về Máy trạng thái hữu hạn cho Linux sử dụng hàng đợi tin nhắn làm sự kiện. Các sự kiện được đưa vào hàng đợi và xử lý theo thứ tự. Trạng thái thay đổi tùy thuộc vào những gì xảy ra cho mỗi sự kiện.

Đây là một ví dụ cho kết nối dữ liệu với các trạng thái như:

  • Chưa hoàn thành
  • Đã khởi tạo
  • Đã kết nối
  • MTU đàm phán
  • Chứng thực

Một tính năng bổ sung nhỏ mà tôi đã thêm là dấu thời gian cho mỗi tin nhắn / sự kiện. Trình xử lý sự kiện sẽ bỏ qua các sự kiện đã quá cũ (chúng đã hết hạn). Điều này có thể xảy ra rất nhiều trong thế giới thực, nơi bạn có thể bị mắc kẹt trong trạng thái bất ngờ.

Ví dụ này chạy trên Linux, sử dụng Makefile bên dưới để biên dịch nó và chơi xung quanh nó.

state_machine.c

#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#include <unistd.h>   // sysconf()
#include <errno.h>    // errno
#include <string.h>   // strerror()
#include <sys/time.h> // gettimeofday()
#include <fcntl.h>    // For O_* constants
#include <sys/stat.h> // For mode constants

#include <mqueue.h>
#include <poll.h>

//------------------------------------------------
// States
//------------------------------------------------
typedef enum
{
    ST_UNKNOWN = 0,
    ST_UNINIT,
    ST_INIT,
    ST_CONNECTED,
    ST_MTU_NEGOTIATED,
    ST_AUTHENTICATED,
    ST_ERROR,
    ST_DONT_CHANGE,
    ST_TERM,
} fsmState_t;

//------------------------------------------------
// Events
//------------------------------------------------
typedef enum
{
    EV_UNKNOWN = 0,
    EV_INIT_SUCCESS,
    EV_INIT_FAIL,
    EV_MASTER_CMD_MSG,
    EV_CONNECT_SUCCESS,
    EV_CONNECT_FAIL,
    EV_MTU_SUCCESS,
    EV_MTU_FAIL,
    EV_AUTH_SUCCESS,
    EV_AUTH_FAIL,
    EV_TX_SUCCESS,
    EV_TX_FAIL,
    EV_DISCONNECTED,
    EV_DISCON_FAILED,
    EV_LAST_ENTRY,
} fsmEvName_t;

typedef struct fsmEvent_type
{
    fsmEvName_t name;
    struct timeval genTime; // Time the event was generated.
                            // This allows us to see how old the event is.
} fsmEvent_t;

// Finite State Machine Data Members
typedef struct fsmData_type
{
    int  connectTries;
    int  MTUtries;
    int  authTries;
    int  txTries;
} fsmData_t;

// Each row of the state table
typedef struct stateTable_type {
    fsmState_t  st;             // Current state
    fsmEvName_t evName;         // Got this event
    int (*conditionfn)(void *);  // If this condition func returns TRUE
    fsmState_t nextState;       // Change to this state and
    void (*fn)(void *);          // Run this function
} stateTable_t;

// Finite State Machine state structure
typedef struct fsm_type
{
    const stateTable_t *pStateTable; // Pointer to state table
    int        numStates;            // Number of entries in the table
    fsmState_t currentState;         // Current state
    fsmEvent_t currentEvent;         // Current event
    fsmData_t *fsmData;              // Pointer to the data attributes
    mqd_t      mqdes;                // Message Queue descriptor
    mqd_t      master_cmd_mqdes;     // Master command message queue
} fsm_t;

// Wildcard events and wildcard state
#define   EV_ANY    -1
#define   ST_ANY    -1
#define   TRUE     (1)
#define   FALSE    (0)

// Maximum priority for message queues (see "man mq_overview")
#define FSM_PRIO  (sysconf(_SC_MQ_PRIO_MAX) - 1)

static void addev                              (fsm_t *fsm, fsmEvName_t ev);
static void doNothing                          (void *fsm) {addev(fsm, EV_MASTER_CMD_MSG);}
static void doInit                             (void *fsm) {addev(fsm, EV_INIT_SUCCESS);}
static void doConnect                          (void *fsm) {addev(fsm, EV_CONNECT_SUCCESS);}
static void doMTU                              (void *fsm) {addev(fsm, EV_MTU_SUCCESS);}
static void reportFailConnect                  (void *fsm) {addev(fsm, EV_ANY);}
static void doAuth                             (void *fsm) {addev(fsm, EV_AUTH_SUCCESS);}
static void reportDisConnect                   (void *fsm) {addev(fsm, EV_ANY);}
static void doDisconnect                       (void *fsm) {addev(fsm, EV_ANY);}
static void doTransaction                      (void *fsm) {addev(fsm, EV_TX_FAIL);}
static void fsmError                           (void *fsm) {addev(fsm, EV_ANY);}

static int currentlyLessThanMaxConnectTries    (void *fsm) {
    fsm_t *l = (fsm_t *)fsm;
    return (l->fsmData->connectTries < 5 ? TRUE : FALSE);
}
static int        isMoreThanMaxConnectTries    (void *fsm) {return TRUE;}
static int currentlyLessThanMaxMTUtries        (void *fsm) {return TRUE;}
static int        isMoreThanMaxMTUtries        (void *fsm) {return TRUE;}
static int currentyLessThanMaxAuthTries        (void *fsm) {return TRUE;}
static int       isMoreThanMaxAuthTries        (void *fsm) {return TRUE;}
static int currentlyLessThanMaxTXtries         (void *fsm) {return FALSE;}
static int        isMoreThanMaxTXtries         (void *fsm) {return TRUE;}
static int didNotSelfDisconnect                (void *fsm) {return TRUE;}

static int  waitForEvent                       (fsm_t *fsm);
static void runEvent                           (fsm_t *fsm);
static void runStateMachine(fsm_t *fsm);
static int newEventIsValid(fsmEvent_t *event);
static void getTime(struct timeval *time);
void printState(fsmState_t st);
void printEvent(fsmEvName_t ev);

// Global State Table
const stateTable_t GST[] = {
    // Current state         Got this event          If this condition func returns TRUE     Change to this state and    Run this function
    { ST_UNINIT,             EV_INIT_SUCCESS,        NULL,                                   ST_INIT,                    &doNothing              },
    { ST_UNINIT,             EV_INIT_FAIL,           NULL,                                   ST_UNINIT,                  &doInit                 },
    { ST_INIT,               EV_MASTER_CMD_MSG,      NULL,                                   ST_INIT,                    &doConnect              },
    { ST_INIT,               EV_CONNECT_SUCCESS,     NULL,                                   ST_CONNECTED,               &doMTU                  },
    { ST_INIT,               EV_CONNECT_FAIL,        &currentlyLessThanMaxConnectTries,      ST_INIT,                    &doConnect              },
    { ST_INIT,               EV_CONNECT_FAIL,        &isMoreThanMaxConnectTries,             ST_INIT,                    &reportFailConnect      },
    { ST_CONNECTED,          EV_MTU_SUCCESS,         NULL,                                   ST_MTU_NEGOTIATED,          &doAuth                 },
    { ST_CONNECTED,          EV_MTU_FAIL,            &currentlyLessThanMaxMTUtries,          ST_CONNECTED,               &doMTU                  },
    { ST_CONNECTED,          EV_MTU_FAIL,            &isMoreThanMaxMTUtries,                 ST_CONNECTED,               &doDisconnect           },
    { ST_CONNECTED,          EV_DISCONNECTED,        &didNotSelfDisconnect,                  ST_INIT,                    &reportDisConnect       },
    { ST_MTU_NEGOTIATED,     EV_AUTH_SUCCESS,        NULL,                                   ST_AUTHENTICATED,           &doTransaction          },
    { ST_MTU_NEGOTIATED,     EV_AUTH_FAIL,           &currentyLessThanMaxAuthTries,          ST_MTU_NEGOTIATED,          &doAuth                 },
    { ST_MTU_NEGOTIATED,     EV_AUTH_FAIL,           &isMoreThanMaxAuthTries,                ST_MTU_NEGOTIATED,          &doDisconnect           },
    { ST_MTU_NEGOTIATED,     EV_DISCONNECTED,        &didNotSelfDisconnect,                  ST_INIT,                    &reportDisConnect       },
    { ST_AUTHENTICATED,      EV_TX_SUCCESS,          NULL,                                   ST_AUTHENTICATED,           &doDisconnect           },
    { ST_AUTHENTICATED,      EV_TX_FAIL,             &currentlyLessThanMaxTXtries,           ST_AUTHENTICATED,           &doTransaction          },
    { ST_AUTHENTICATED,      EV_TX_FAIL,             &isMoreThanMaxTXtries,                  ST_AUTHENTICATED,           &doDisconnect           },
    { ST_AUTHENTICATED,      EV_DISCONNECTED,        &didNotSelfDisconnect,                  ST_INIT,                    &reportDisConnect       },
    { ST_ANY,                EV_DISCON_FAILED,       NULL,                                   ST_DONT_CHANGE,             &doDisconnect           },
    { ST_ANY,                EV_ANY,                 NULL,                                   ST_UNINIT,                  &fsmError               }    // Wildcard state for errors
};

#define GST_COUNT (sizeof(GST)/sizeof(stateTable_t))

int main()
{
    int ret = 0;
    fsmData_t dataAttr;
    dataAttr.connectTries = 0;
    dataAttr.MTUtries     = 0;
    dataAttr.authTries    = 0;
    dataAttr.txTries      = 0;

    fsm_t lfsm;
    memset(&lfsm, 0, sizeof(fsm_t));
    lfsm.pStateTable       = GST;
    lfsm.numStates         = GST_COUNT;
    lfsm.currentState      = ST_UNINIT;
    lfsm.currentEvent.name = EV_ANY;
    lfsm.fsmData           = &dataAttr;

    struct mq_attr attr;
    attr.mq_maxmsg = 30;
    attr.mq_msgsize = sizeof(fsmEvent_t);

    // Dev info
    //printf("Size of fsmEvent_t [%ld]\n", sizeof(fsmEvent_t));

    ret = mq_unlink("/abcmq");
    if (ret == -1) {
        fprintf(stderr, "Error on mq_unlink(), errno[%d] strerror[%s]\n",
                errno, strerror(errno));
    }

    lfsm.mqdes = mq_open("/abcmq", O_CREAT | O_RDWR, S_IWUSR | S_IRUSR, &attr);
    if (lfsm.mqdes == (mqd_t)-1) {
        fprintf(stderr, "Error on mq_open(), errno[%d] strerror[%s]\n",
                errno, strerror(errno));
        return -1;
    }

    doInit(&lfsm);  // This will generate the first event
    runStateMachine(&lfsm);

    return 0;
}


static void runStateMachine(fsm_t *fsm)
{
    int ret = 0;

    if (fsm == NULL) {
        fprintf(stderr, "[%s] NULL argument\n", __func__);
        return;
    }

    // Cycle through the state machine
    while (fsm->currentState != ST_TERM) {
        printf("current state [");
        printState(fsm->currentState);
        printf("]\n");

        ret = waitForEvent(fsm);
        if (ret == 0) {
            printf("got event [");
            printEvent(fsm->currentEvent.name);
            printf("]\n");

            runEvent(fsm);
        }
        sleep(2);
    }
}


static int waitForEvent(fsm_t *fsm)
{
    //const int numFds = 2;
    const int numFds = 1;
    struct pollfd fds[numFds];
    int timeout_msecs = -1; // -1 is forever
    int ret = 0;
    int i = 0;
    ssize_t num = 0;
    fsmEvent_t newEv;

    if (fsm == NULL) {
        fprintf(stderr, "[%s] NULL argument\n", __func__);
        return -1;
    }

    fsm->currentEvent.name = EV_ANY;

    fds[0].fd     = fsm->mqdes;
    fds[0].events = POLLIN;
    //fds[1].fd     = fsm->master_cmd_mqdes;
    //fds[1].events = POLLIN;
    ret = poll(fds, numFds, timeout_msecs);

    if (ret > 0) {
        // An event on one of the fds has occurred
        for (i = 0; i < numFds; i++) {
            if (fds[i].revents & POLLIN) {
                // Data may be read on device number i
                num = mq_receive(fds[i].fd, (void *)(&newEv),
                                 sizeof(fsmEvent_t), NULL);
                if (num == -1) {
                    fprintf(stderr, "Error on mq_receive(), errno[%d] "
                            "strerror[%s]\n", errno, strerror(errno));
                    return -1;
                }

                if (newEventIsValid(&newEv)) {
                    fsm->currentEvent = newEv;
                } else {
                    return -1;
                }
            }
        }
    } else {
        fprintf(stderr, "Error on poll(), ret[%d] errno[%d] strerror[%s]\n",
                ret, errno, strerror(errno));
        return -1;
    }

    return 0;
}


static int newEventIsValid(fsmEvent_t *event)
{
    if (event == NULL) {
        fprintf(stderr, "[%s] NULL argument\n", __func__);
        return FALSE;
    }

    printf("[%s]\n", __func__);

    struct timeval now;
    getTime(&now);

    if ( (event->name < EV_LAST_ENTRY) &&
         ((now.tv_sec - event->genTime.tv_sec) < (60*5))
       )
    {
        return TRUE;
    } else {
        return FALSE;
    }
}


//------------------------------------------------
// Performs event handling on the FSM (finite state machine).
// Make sure there is a wildcard state at the end of
// your table, otherwise; the event will be ignored.
//------------------------------------------------
static void runEvent(fsm_t *fsm)
{
    int i;
    int condRet = 0;

    if (fsm == NULL) {
        fprintf(stderr, "[%s] NULL argument\n", __func__);
        return;
    }

    printf("[%s]\n", __func__);

    // Find a relevant entry for this state and event
    for (i = 0; i < fsm->numStates; i++) {
        // Look in the table for our current state or ST_ANY
        if (  (fsm->pStateTable[i].st == fsm->currentState) ||
              (fsm->pStateTable[i].st == ST_ANY)
           )
        {
            // Is this the event we are looking for?
            if ( (fsm->pStateTable[i].evName == fsm->currentEvent.name) ||
                 (fsm->pStateTable[i].evName == EV_ANY)
               )
            {
                if (fsm->pStateTable[i].conditionfn != NULL) {
                    condRet = fsm->pStateTable[i].conditionfn(fsm->fsmData);
                }

                // See if there is a condition associated
                // or we are not looking for any condition
                //
                if ( (condRet != 0) || (fsm->pStateTable[i].conditionfn == NULL))
                {
                    // Set the next state (if applicable)
                    if (fsm->pStateTable[i].nextState != ST_DONT_CHANGE) {
                        fsm->currentState = fsm->pStateTable[i].nextState;
                        printf("new state [");
                        printState(fsm->currentState);
                        printf("]\n");
                    }

                    // Call the state callback function
                    fsm->pStateTable[i].fn(fsm);
                    break;
                }
            }
        }
    }
}


//------------------------------------------------
//               EVENT HANDLERS
//------------------------------------------------
static void getTime(struct timeval *time)
{
    if (time == NULL) {
        fprintf(stderr, "[%s] NULL argument\n", __func__);
        return;
    }

    printf("[%s]\n", __func__);

    int ret = gettimeofday(time, NULL);
    if (ret != 0) {
        fprintf(stderr, "gettimeofday() failed: errno [%d], strerror [%s]\n",
                errno, strerror(errno));
        memset(time, 0, sizeof(struct timeval));
    }
}


static void addev (fsm_t *fsm, fsmEvName_t ev)
{
    int ret = 0;

    if (fsm == NULL) {
        fprintf(stderr, "[%s] NULL argument\n", __func__);
        return;
    }

    printf("[%s] ev[%d]\n", __func__, ev);

    if (ev == EV_ANY) {
        // Don't generate a new event, just return...
        return;
    }

    fsmEvent_t newev;
    getTime(&(newev.genTime));
    newev.name = ev;

    ret = mq_send(fsm->mqdes, (void *)(&newev), sizeof(fsmEvent_t), FSM_PRIO);
    if (ret == -1) {
        fprintf(stderr, "[%s] mq_send() failed: errno [%d], strerror [%s]\n",
                __func__, errno, strerror(errno));
    }
}
//------------------------------------------------
//           end EVENT HANDLERS
//------------------------------------------------

void printState(fsmState_t st)
{
    switch(st) {
        case    ST_UNKNOWN:
        printf("ST_UNKNOWN");
            break;
        case    ST_UNINIT:
        printf("ST_UNINIT");
            break;
        case    ST_INIT:
        printf("ST_INIT");
            break;
        case    ST_CONNECTED:
        printf("ST_CONNECTED");
            break;
        case    ST_MTU_NEGOTIATED:
        printf("ST_MTU_NEGOTIATED");
            break;
        case    ST_AUTHENTICATED:
        printf("ST_AUTHENTICATED");
            break;
        case    ST_ERROR:
        printf("ST_ERROR");
            break;
        case    ST_TERM:
        printf("ST_TERM");
            break;
        default:
        printf("unknown state");
            break;
    }
}

void printEvent(fsmEvName_t ev)
{
    switch (ev) {
        case    EV_UNKNOWN:
        printf("EV_UNKNOWN");
            break;
        case    EV_INIT_SUCCESS:
        printf("EV_INIT_SUCCESS");
            break;
        case    EV_INIT_FAIL:
        printf("EV_INIT_FAIL");
            break;
        case    EV_MASTER_CMD_MSG:
        printf("EV_MASTER_CMD_MSG");
            break;
        case    EV_CONNECT_SUCCESS:
        printf("EV_CONNECT_SUCCESS");
            break;
        case    EV_CONNECT_FAIL:
        printf("EV_CONNECT_FAIL");
            break;
        case    EV_MTU_SUCCESS:
        printf("EV_MTU_SUCCESS");
            break;
        case    EV_MTU_FAIL:
        printf("EV_MTU_FAIL");
            break;
        case    EV_AUTH_SUCCESS:
        printf("EV_AUTH_SUCCESS");
            break;
        case    EV_AUTH_FAIL:
        printf("EV_AUTH_FAIL");
            break;
        case    EV_TX_SUCCESS:
        printf("EV_TX_SUCCESS");
            break;
        case    EV_TX_FAIL:
        printf("EV_TX_FAIL");
            break;
        case    EV_DISCONNECTED:
        printf("EV_DISCONNECTED");
            break;
        case    EV_LAST_ENTRY:
        printf("EV_LAST_ENTRY");
            break;
        default:
        printf("unknown event");
            break;
    }
}

Makefile

CXX = gcc
COMPFLAGS = -c -Wall -g

state_machine: state_machine.o
    $(CXX) -lrt state_machine.o -o state_machine

state_machine.o: state_machine.c
    $(CXX) $(COMPFLAGS) state_machine.c

clean:
    rm state_machine state_machine.o

1

Câu hỏi của bạn khá chung chung,
Dưới đây là hai bài viết tham khảo có thể hữu ích,

  1. Triển khai máy nhà nước nhúng

    Bài viết này mô tả một cách tiếp cận đơn giản để thực hiện một máy trạng thái cho một hệ thống nhúng. Đối với mục đích của bài viết này, một máy trạng thái được định nghĩa là một thuật toán có thể ở một trong số ít các trạng thái. Trạng thái là điều kiện gây ra mối quan hệ được quy định của đầu vào với đầu ra và của đầu vào sang trạng thái tiếp theo.
    Một người đọc hiểu biết sẽ nhanh chóng lưu ý rằng các máy trạng thái được mô tả trong bài viết này là các máy Mealy. Máy Mealy là một máy trạng thái trong đó các đầu ra là một chức năng của cả trạng thái hiện tại và đầu vào, trái ngược với máy Moore, trong đó các đầu ra chỉ là một chức năng của trạng thái.

    • Máy trạng thái mã hóa trong C và C ++

      Mối bận tâm của tôi trong bài viết này là với các nguyên tắc cơ bản của máy trạng thái và một số hướng dẫn lập trình đơn giản cho mã hóa máy trạng thái trong C hoặc C ++. Tôi hy vọng rằng các kỹ thuật đơn giản này có thể trở nên phổ biến hơn, để bạn (và những người khác) có thể dễ dàng nhìn thấy cấu trúc máy trạng thái ngay từ mã nguồn.



1

Đây là một bài viết cũ với rất nhiều câu trả lời, nhưng tôi nghĩ tôi đã thêm cách tiếp cận của riêng mình vào máy trạng thái hữu hạn trong C. Tôi đã tạo một tập lệnh Python để tạo mã C cho bất kỳ trạng thái nào. Kịch bản đó được ghi lại trên GituHub tại FsmTemplateC

Ví dụ này dựa trên các phương pháp khác mà tôi đã đọc. Nó không sử dụng câu lệnh goto hoặc switch mà thay vào đó có các hàm chuyển đổi trong ma trận con trỏ (bảng tra cứu). Mã này dựa trên một macro macro khởi tạo nhiều dòng và các tính năng C99 (bộ khởi tạo được chỉ định và nghĩa đen), vì vậy nếu bạn không thích những thứ này, bạn có thể không thích cách tiếp cận này.

Dưới đây là tập lệnh Python của một ví dụ quay vòng tạo mã C xương bằng cách sử dụng FsmTemplateC :

# dict parameter for generating FSM
fsm_param = {
    # main FSM struct type string
    'type': 'FsmTurnstile',
    # struct type and name for passing data to state machine functions
    # by pointer (these custom names are optional)
    'fopts': {
        'type': 'FsmTurnstileFopts',
        'name': 'fopts'
    },
    # list of states
    'states': ['locked', 'unlocked'],
    # list of inputs (can be any length > 0)
    'inputs': ['coin', 'push'],
    # map inputs to commands (next desired state) using a transition table
    # index of array corresponds to 'inputs' array
    # for this example, index 0 is 'coin', index 1 is 'push'
    'transitiontable': {
        # current state |  'coin'  |  'push'  |
        'locked':       ['unlocked',        ''],
        'unlocked':     [        '',  'locked']
    }
}

# folder to contain generated code
folder = 'turnstile_example'
# function prefix
prefix = 'fsm_turnstile'

# generate FSM code
code = fsm.Fsm(fsm_param).genccode(folder, prefix)

Tiêu đề đầu ra được tạo có chứa typedefs:

/* function options (EDIT) */
typedef struct FsmTurnstileFopts {
    /* define your options struct here */
} FsmTurnstileFopts;

/* transition check */
typedef enum eFsmTurnstileCheck {
    EFSM_TURNSTILE_TR_RETREAT,
    EFSM_TURNSTILE_TR_ADVANCE,
    EFSM_TURNSTILE_TR_CONTINUE,
    EFSM_TURNSTILE_TR_BADINPUT
} eFsmTurnstileCheck;

/* states (enum) */
typedef enum eFsmTurnstileState {
    EFSM_TURNSTILE_ST_LOCKED,
    EFSM_TURNSTILE_ST_UNLOCKED,
    EFSM_TURNSTILE_NUM_STATES
} eFsmTurnstileState;

/* inputs (enum) */
typedef enum eFsmTurnstileInput {
    EFSM_TURNSTILE_IN_COIN,
    EFSM_TURNSTILE_IN_PUSH,
    EFSM_TURNSTILE_NUM_INPUTS,
    EFSM_TURNSTILE_NOINPUT
} eFsmTurnstileInput;

/* finite state machine struct */
typedef struct FsmTurnstile {
    eFsmTurnstileInput input;
    eFsmTurnstileCheck check;
    eFsmTurnstileState cur;
    eFsmTurnstileState cmd;
    eFsmTurnstileState **transition_table;
    void (***state_transitions)(struct FsmTurnstile *, FsmTurnstileFopts *);
    void (*run)(struct FsmTurnstile *, FsmTurnstileFopts *, const eFsmTurnstileInput);
} FsmTurnstile;

/* transition functions */
typedef void (*pFsmTurnstileStateTransitions)(struct FsmTurnstile *, FsmTurnstileFopts *);
  • enum eFsmTurnstileCheckđược sử dụng để xác định xem một quá trình chuyển đổi có bị chặn hay không EFSM_TURNSTILE_TR_RETREAT, được phép tiến hành EFSM_TURNSTILE_TR_ADVANCEhoặc cuộc gọi chức năng không được thực hiện trước khi chuyển đổi với EFSM_TURNSTILE_TR_CONTINUE.
  • enum eFsmTurnstileStatechỉ đơn giản là danh sách các tiểu bang.
  • enum eFsmTurnstileInputchỉ đơn giản là danh sách các đầu vào.
  • Cấu FsmTurnstiletrúc là trái tim của máy trạng thái với kiểm tra chuyển tiếp, bảng tra cứu chức năng, trạng thái hiện tại, trạng thái được chỉ huy và bí danh cho chức năng chính chạy máy.
  • Mỗi con trỏ hàm (bí danh) FsmTurnstilechỉ nên được gọi từ struct và phải có đầu vào đầu tiên là con trỏ tới chính nó để duy trì trạng thái bền vững, kiểu hướng đối tượng.

Bây giờ cho các khai báo hàm trong tiêu đề:

/* fsm declarations */
void fsm_turnstile_locked_locked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_locked_unlocked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_unlocked_locked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_unlocked_unlocked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_run (FsmTurnstile *fsm, FsmTurnstileFopts *fopts, const eFsmTurnstileInput input);

Tên hàm có định dạng {prefix}_{from}_{to}, ở đâu {from}là trạng thái (hiện tại) trước và {to}là trạng thái tiếp theo. Lưu ý rằng nếu bảng chuyển đổi không cho phép chuyển đổi nhất định, một con trỏ NULL thay vì con trỏ hàm sẽ được đặt. Cuối cùng, phép màu xảy ra với một vĩ mô. Ở đây chúng ta xây dựng bảng chuyển tiếp (ma trận của enum trạng thái) và các hàm chuyển trạng thái tìm bảng (một ma trận các con trỏ hàm):

/* creation macro */
#define FSM_TURNSTILE_CREATE() \
{ \
    .input = EFSM_TURNSTILE_NOINPUT, \
    .check = EFSM_TURNSTILE_TR_CONTINUE, \
    .cur = EFSM_TURNSTILE_ST_LOCKED, \
    .cmd = EFSM_TURNSTILE_ST_LOCKED, \
    .transition_table = (eFsmTurnstileState * [EFSM_TURNSTILE_NUM_STATES]) { \
        (eFsmTurnstileState [EFSM_TURNSTILE_NUM_INPUTS]) { \
            EFSM_TURNSTILE_ST_UNLOCKED, \
            EFSM_TURNSTILE_ST_LOCKED \
        }, \
        (eFsmTurnstileState [EFSM_TURNSTILE_NUM_INPUTS]) { \
            EFSM_TURNSTILE_ST_UNLOCKED, \
            EFSM_TURNSTILE_ST_LOCKED \
        } \
    }, \
    .state_transitions = (pFsmTurnstileStateTransitions * [EFSM_TURNSTILE_NUM_STATES]) { \
        (pFsmTurnstileStateTransitions [EFSM_TURNSTILE_NUM_STATES]) { \
            fsm_turnstile_locked_locked, \
            fsm_turnstile_locked_unlocked \
        }, \
        (pFsmTurnstileStateTransitions [EFSM_TURNSTILE_NUM_STATES]) { \
            fsm_turnstile_unlocked_locked, \
            fsm_turnstile_unlocked_unlocked \
        } \
    }, \
    .run = fsm_turnstile_run \
}

Khi tạo FSM, macro FSM_EXAMPLE_CREATE()phải được sử dụng.

Bây giờ, trong mã nguồn, mọi hàm chuyển đổi trạng thái được khai báo ở trên sẽ được điền. Cấu FsmTurnstileFoptstrúc có thể được sử dụng để truyền dữ liệu đến / từ máy trạng thái. Mọi chuyển đổi phải được đặt fsm->checkbằng hoặc EFSM_EXAMPLE_TR_RETREATđể chặn nó chuyển tiếp hoặc EFSM_EXAMPLE_TR_ADVANCEcho phép nó chuyển sang trạng thái được chỉ huy. Một ví dụ hoạt động có thể được tìm thấy tại (FsmTemplateC) [ https://github.com/ChisholmKyle/FsmTemplateC] .

Đây là cách sử dụng thực tế rất đơn giản trong mã của bạn:

/* create fsm */
FsmTurnstile fsm = FSM_TURNSTILE_CREATE();
/* create fopts */
FsmTurnstileFopts fopts = {
    .msg = ""
};
/* initialize input */
eFsmTurnstileInput input = EFSM_TURNSTILE_NOINPUT;

/* main loop */
for (;;) {
    /* wait for timer signal, inputs, interrupts, whatever */
    /* optionally set the input (my_input = EFSM_TURNSTILE_IN_PUSH for example) */
    /* run state machine */
    my_fsm.run(&my_fsm, &my_fopts, my_input);
}

Tất cả các doanh nghiệp tiêu đề và tất cả các chức năng đó chỉ để có một giao diện đơn giản và nhanh chóng là đáng giá trong tâm trí của tôi.


0

Bạn có thể sử dụng thư viện mã nguồn mở OpenFST .

OpenFst là một thư viện để xây dựng, kết hợp, tối ưu hóa và tìm kiếm các bộ chuyển đổi trạng thái hữu hạn có trọng số (FST). Bộ chuyển đổi trạng thái hữu hạn có trọng số là automata trong đó mỗi bộ chuyển đổi có nhãn đầu vào, nhãn đầu ra và trọng số. Công cụ chấp nhận trạng thái hữu hạn quen thuộc hơn được biểu diễn dưới dạng đầu dò với mỗi nhãn đầu vào và đầu ra của mỗi chuyển đổi bằng nhau. Các chấp nhận trạng thái hữu hạn được sử dụng để biểu diễn các bộ chuỗi (cụ thể, các bộ thông thường hoặc hợp lý); Các bộ chuyển đổi trạng thái hữu hạn được sử dụng để biểu diễn các quan hệ nhị phân giữa các cặp chuỗi (cụ thể là các bộ chuyển đổi hợp lý). Các trọng số có thể được sử dụng để thể hiện chi phí thực hiện một quá trình chuyển đổi cụ thể.


0
void (* StateController)(void); 
void state1(void);
void state2(void);

void main()
{
 StateController=&state1;
 while(1)
 {
  (* StateController)();
 }
}

void state1(void)
{
 //do something in state1
 StateController=&state2;
}

void state2(void)
{
 //do something in state2
 //Keep changing function direction based on state transition
 StateController=&state1;
}

Bạn có thể tối ưu hóa thêm để đảm bảo an toàn bằng cách sử dụng một loạt các con trỏ hàm liên tục cho các hàm
AlphaGoku

0

Cá nhân tôi sử dụng các cấu trúc tự tham chiếu kết hợp với các mảng con trỏ. Tôi đã tải lên một hướng dẫn trên github một lúc trước, liên kết:

https://github.com/mmelchger/polling_state_machine_c

Lưu ý: Tôi nhận ra rằng chủ đề này khá cũ, nhưng tôi hy vọng sẽ nhận được đầu vào và suy nghĩ về thiết kế của máy trạng thái cũng như có thể cung cấp một ví dụ cho thiết kế máy trạng thái có thể có trong C.


0

Bạn có thể xem xét UML-state-machine-in-c , khung máy trạng thái "nhẹ" trong C. Tôi đã viết khung này để hỗ trợ cả máy trạng thái hữu hạnmáy trạng thái phân cấp . So sánh với các bảng trạng thái hoặc các trường hợp chuyển đổi đơn giản, một cách tiếp cận khung có khả năng mở rộng hơn. Nó có thể được sử dụng cho các máy trạng thái hữu hạn đơn giản cho các máy trạng thái phân cấp phức tạp.

Máy nhà nước được đại diện bởi state_machine_tcấu trúc. Nó chỉ chứa hai thành viên "Sự kiện" và một con trỏ tới "state_t".

struct state_machine_t
{
   uint32_t Event;          //!< Pending Event for state machine
   const state_t* State;    //!< State of state machine.
};

state_machine_tphải là thành viên đầu tiên của cấu trúc máy trạng thái của bạn. ví dụ

struct user_state_machine
{
  state_machine_t Machine;    // Base state machine. Must be the first member of user derived state machine.

  // User specific state machine members
  uint32_t param1;
  uint32_t param2;
  ...
};

state_t chứa một trình xử lý cho trạng thái và các trình xử lý tùy chọn cho hành động xuất nhập cảnh.

//! finite state structure
struct finite_state{
  state_handler Handler;      //!< State handler to handle event of the state
  state_handler Entry;        //!< Entry action for state
  state_handler Exit;          //!< Exit action for state.
};

Nếu khung được cấu hình cho máy trạng thái phân cấp thì state_tchứa con trỏ tới trạng thái cha và con.

Framework cung cấp API dispatch_eventđể gửi sự kiện đến máy trạng thái và switch_statekích hoạt chuyển trạng thái.

Để biết thêm chi tiết về cách triển khai máy trạng thái phân cấp, hãy tham khảo kho GitHub .

mã ví dụ,

https://github.com/kiishor/UML-State-Machine-in-C/blob/master/demo/simple_state_machine/readme.md https://github.com/kiishor/UML-State-Machine-in-C /blob/master/demo/simple_state_machine_enhified/readme.md


-1

Đây là một phương pháp cho một máy trạng thái sử dụng các macro để mỗi hàm có thể có một bộ trạng thái riêng: https://www.codeproject.com/Articles/37037/Macros-to-simulation-multi-tasking-blocking-code -at

Nó có tiêu đề "mô phỏng đa tác vụ" nhưng đó không phải là cách sử dụng duy nhất.

Phương pháp này sử dụng các cuộc gọi lại để lấy trong mỗi chức năng nơi nó rời đi. Mỗi chức năng chứa một danh sách các trạng thái duy nhất cho mỗi chức năng. Một "vòng lặp nhàn rỗi" trung tâm được sử dụng để chạy các máy trạng thái. "Vòng lặp nhàn rỗi" không biết các máy trạng thái hoạt động như thế nào, đó là các chức năng riêng lẻ "biết phải làm gì". Để viết mã cho các hàm, người ta chỉ cần tạo một danh sách các trạng thái và sử dụng các macro để "tạm dừng" và "tiếp tục". Tôi đã sử dụng các macro này tại Cisco khi tôi viết Thư viện thu phát cho bộ chuyển đổi Nexus 7000.

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.