Exercise 23: Meet Duff’s Device
Đây là một bài phức tạp (chỉ có 2 hàm phức tạp), và đây là một bài test não.
Trong ngôn ngữ C, thật sự quá nhiều điều rắc rối vì cú pháp của nó, cho nên việc học ngôn ngữ C thành chuyên gia là một điều không dể.
Tom Duff đã tạo ra một chương trình dựa vào sự rác rối của vòng lặp do-while và switch-case. Chương trình này rất nổi tiếng (thông tin trên Wikipedia), tên gọi của nó là Duff’s device. Sau khi học bài này, bạn 1 sẽ thấy nó tuyệt vời, 2 là thấy nó như ác quỷ. Và hãy xem lời khuyên ở cuối bài.
Warning!
Ngôn ngữ C thật sự rất điên rồ, và bạn hoàn toàn có thể tạo ra những đoạn chương trình giống như vậy. Nhưng những chương trình như vậy thực sự rất khó hiểu, có thể khiến bạn phát điên. Tuy nhiên nếu bạn hiểu được những bài như thế này, thì trình độ của bạn đã ở rất cao rồi. Bạn đã hiểu sâu về C cũng như về máy tính (bộ nhớ). Nhưng bạn không nên sử dụng nó từ này kể về sau. Vì nó sẽ gây khó chịu cho người đọc.
Dưới đây là chương trình, 2 hàm khó nhất cũng chính là thứ chính yếu trong chương trình này duffs_device và zeds_device.
#include <stdio.h> #include <string.h> #include "dbg.h" int normal_copy(char *from, char *to, int count) { int i = 0; for (i = 0; i < count; i++) { to[i] = from[i]; } return i; } int duffs_device(char *from, char *to, int count) { { int n = (count + 7) / 8; switch (count % 8) { case 0: do { *to++ = *from++; case 7: *to++ = *from++; case 6: *to++ = *from++; case 5: *to++ = *from++; case 4: *to++ = *from++; case 3: *to++ = *from++; case 2: *to++ = *from++; case 1: *to++ = *from++; } while (--n > 0); } } return count; } int zeds_device(char *from, char *to, int count) { { int n = (count + 7) / 8; switch (count % 8) { case 0: again: *to++ = *from++; case 7: *to++ = *from++; case 6: *to++ = *from++; case 5: *to++ = *from++; case 4: *to++ = *from++; case 3: *to++ = *from++; case 2: *to++ = *from++; case 1: *to++ = *from++; if (--n > 0) goto again; } } return count; } int valid_copy(char *data, int count, char expects) { int i = 0; for (i = 0; i < count; i++) { if (data[i] != expects) { log_err("[%d] %c != %c", i, data[i], expects); return 0; } } return 1; } int main(int argc, char *argv[]) { char from[1000] = { 'a' }; char to[1000] = { 'c' }; int rc = 0; // set up the from to have some stuff memset(from, 'x', 1000); // set it to a failure mode memset(to, 'y', 1000); check(valid_copy(to, 1000, 'y'), "Not initialized right."); // use normal copy to rc = normal_copy(from, to, 1000); check(rc == 1000, "Normal copy failed: %d", rc); check(valid_copy(to, 1000, 'x'), "Normal copy failed."); // reset memset(to, 'y', 1000); // duffs version rc = duffs_device(from, to, 1000); check(rc == 1000, "Duff's device failed: %d", rc); check(valid_copy(to, 1000, 'x'), "Duff's device failed copy."); // reset memset(to, 'y', 1000); // my version rc = zeds_device(from, to, 1000); check(rc == 1000, "Zed's device failed: %d", rc); check(valid_copy(to, 1000, 'x'), "Zed's device failed copy."); return 0; error: return 1; }
Trong đoạn code này, chúng ta có 3 phiên bản cho hàm copy:
- normal_copy Dùng vòng lặp for để copy các kí tự từ mảng này qua mảng khác, đây là cách rất bình thường.
- duffs_device Hàm huyền thoại của chúng ta – Duff’s device, một hàm khiến các lập trình viên rất khó chịu (như đã nói ở đầu bài).
- zeds_device Một phiên bản khác của Duff’s device.
Các bạn hãy học kĩ 3 hàm này rồi hãy đi tiếp. Và thử giải thích nó xem, các bạn đã hiểu nó như thế nào?
What You Should See
Bài này không hề có một output nào. Cho nên các bạn hãy chạy nó với Valgrind hay debugger để xem được nhiều thứ hơn.
Solving the Puzzle
Điều đầu tiên để hiểu là C thật sự quá lỏng lẽo với những cú pháp của nó. Trong trường hợp này là do-while và switch-case, chúng lồng vào nhau, nhưng nó vẫn chạy đúng.
Điều thứ 2 là bạn nhìn thấy lệnh switch-case không hề có break, cho nên nó sẽ nhảy vào một case nào đó, sau đó nó sẽ tiếp tục thực hiện những lệnh phía dưới nó cho đến khi hết case.
Điều cuối cùng là count % 8 và biến n.
Tất cả những điều trên làm cho chương trình này trở nên khó chịu.
Điều bây giờ bạn cần làm là debug từng dòng code và ghi lại các giá trị biến và giá trị trong mảng để hiểu nó một cách sâu sắc.
Lời khuyên: Không bao giờ sử dụng chương trình này cũng như những chương trình tương tự như vậy.