2010-11-11 13 views
9

Trước khi mảng chiều dài thay đổi được hỗ trợ, tôi sẽ tự động phân bổ cho họ như thế này:Làm thế nào để bạn ngăn chặn các mảng có độ dài thay đổi bị lỗi khi không có đủ bộ nhớ?

int foo(size_t n) 
{ 
    int *arr = malloc(n * sizeof int); 
    if (!arr) return ENOMEM; /* not enough memory */ 
    . 
    . else do stuff with arr[] 
    . 
    free(arr); 
    return 0; 
} 

Với mảng chiều dài thay đổi bây giờ tôi có thể làm cho nó trông sạch hơn:

int bar(size_t n) 
{ 
    int arr[n]; 
    . 
    . do stuff with arr[] 
    . 
    return 0; 
} 

Nhưng bây giờ tôi không có kiểm tra "hết bộ nhớ". Trong thực tế, chương trình bị treo nếu n quá lớn.

Làm thế nào tôi có thể được bảo lãnh một cách duyên dáng từ thanh (n) nếu n quá lớn?

+0

Ràng buộc của bạn có thể (hệ thống phụ thuộc, tất nhiên) không phải là "bộ nhớ" nhưng "kích thước của ngăn xếp". Và bây giờ câu hỏi có ý nghĩa hơn với tôi bởi vì tôi đã nghĩ đến việc thực hiện "trước" của bạn trên một hộp tiêu dùng hiện đại hợp lý và tự hỏi "Anh ta làm gì mà anh ấy cần một phần lớn GB?" *. Dù sao. Nếu ngăn xếp giới hạn bạn, cách "cũ" có thể tốt hơn. – dmckee

+0

Đây chính xác là lý do tại sao VLAs không được sử dụng nhiều (một trình hỗ trợ kém trong trình biên dịch). VL C99 đơn giản là không ổn định. – VSG24

Trả lời

12

Tình hình là chính xác không thay đổi từ bất kỳ biến địa phương khác - một bản tuyên bố như thế này:

int baz(void) 
{ 
    int arr[100000]; 
    . 
    . do stuff with arr[] 
    . 
    return 0; 
} 

có chính xác cùng một vấn đề. "Giải pháp" giống như nó luôn luôn - không recurse quá sâu, và không phân bổ cấu trúc dữ liệu rất lớn với thời gian lưu trữ tự động (tiếp tục sử dụng malloc() cho những trường hợp này). Giá trị "rất lớn" phụ thuộc rất nhiều vào môi trường của bạn.

Nói cách khác, không khai báo int array[n]; trừ khi bạn biết rằng n bị ràng buộc với giá trị hợp lý, như vậy bạn sẽ vui lòng khai báo một mảng có kích thước tối đa đó là loại thông thường, không được sửa đổi mảng.

(Có, điều này có nghĩa là các mảng kiểu biến đổi không hữu ích như lần đầu tiên xuất hiện, vì bạn thu được rất ít so với việc khai báo mảng ở kích thước tối đa cần thiết).

+0

Điều này đặt nó vào góc nhìn, cảm ơn. Tôi có thể quyết định kích thước tối đa và bắt đầu hàm với 'if (n <= MAX) {int arr [n]; ...} ' – Henry

6

Bạn có thể ngăn chúng không bị sập bằng cách không sử dụng chúng. :)

Trong mọi trường hợp, hầu như không có cách nào an toàn để sử dụng các mảng có độ dài thay đổi để làm cho cuộc sống của bạn dễ dàng hơn trừ khi bạn có giới hạn mạnh về kích thước. Mặt khác, bạn có thể sử dụng chúng có điều kiện, theo những cách như thế này:

char vla_buf[n < 1000 ? n : 1]; 
char *buf = sizeof vla_buf < n ? malloc(n) : vla_buf; 
if (!buf) goto error; 
/* ... Do stuff with buf ... */ 
if (buf != vla_buf) free(buf); 

Trong khi điều này có vẻ như đau đớn vô dụng, nó có thể tạo sự khác biệt hiệu suất rất lớn, đặc biệt là trong các ứng dụng ren nơi có nhiều cuộc gọi đến mallocfree thể dẫn đến tranh chấp khóa. (Một lợi ích đáng chú ý của thủ thuật này là bạn có thể hỗ trợ các trình biên dịch cũ không có VLAs bằng cách thay thế [n < 1000 ? n : 1] bằng 1000, ví dụ như với macro.)

Một trường hợp tối nghĩa khác mà VLAs có thể hữu ích trong các thuật toán đệ quy mà bạn biết tổng số mục nhập được yêu cầu trên tất cả các cấp đệ quy được giới hạn bởi n, trong đó n đủ nhỏ để bạn tin rằng nó sẽ không tràn ngăn xếp, nhưng ở đó có thể lên tới n mức độ đệ quy và cấp độ cá nhân sử dụng thành phần n. Trước C99, cách duy nhất để xử lý trường hợp này mà không cần lấy n^2 không gian ngăn xếp là sử dụng malloc. Với VLAs, bạn có thể giải quyết vấn đề hoàn toàn trên stack.

Hãy ghi nhớ, những trường hợp VLAs này thực sự có lợi là khá hiếm hoi. Thông thường VLA chỉ là một cách để lừa dối chính bạn rằng việc quản lý bộ nhớ rất dễ dàng, cho đến khi bạn nhận được bit bởi các lỗ hổng kết quả (tầm thường để khai thác) mà bạn đã tạo ra.:-)

Edit: Đối với câu hỏi ban đầu địa chỉ tốt hơn OP của:

#define MAX_VLA 10000 
int bar(size_t n) 
{ 
    int arr[n <= MAX_VLA ? n : 1]; 
    if (sizeof arr/sizeof *arr < n) return ENOMEM; 
    /* ... */ 
    return 0; 
} 
+0

Trong ví dụ đầu tiên của bạn, bạn cũng có thể sử dụng' [1000] 'ở khắp mọi nơi, vì bạn đã xác định rằng giá trị đó sẽ không (không nên) sụp đổ. Thuật toán đệ quy dường như là trường hợp sử dụng thực tế. – caf

+0

Bạn đã cho tôi một số ý tưởng hay, nhưng tôi nghĩ đơn giản nhất là chỉ cần khởi động hàm với 'if (n <= MAX_VLA) {int arr [n]; ...} '. Cảm ơn. – Henry

+0

Lưu ý rằng có lỗi trong ví dụ của bạn. :-) Nó phải là 'if ((sizeof arr)/(sizeof int) Henry

0

Trong thực tế nó là tốn kém để kiểm tra ra khỏi tình trạng bộ nhớ ở khắp mọi nơi. Cách enterprisy để đối phó với dữ liệu lớn là để hạn chế kích thước dữ liệu bằng cách xác định kích thước cứng trên kích thước tại điểm kiểm tra đầu duy nhất và không nhanh chóng và duyên dáng khi nắp được nhấn.

Điều tôi vừa gợi ý đơn giản và ngu ngốc. Nhưng những gì mọi sản phẩm bình thường (phi khoa học hoặc đặc biệt) luôn làm. Và những gì nó thường được mong đợi bởi khách hàng.