Tại sao việc sử dụng alloca () không được coi là thực hành tốt?


401

alloca() cấp phát bộ nhớ trên ngăn xếp thay vì trên heap, như trong trường hợp malloc() . Vì vậy, khi tôi trở về từ thói quen, bộ nhớ được giải phóng. Vì vậy, thực sự điều này giải quyết vấn đề của tôi về giải phóng bộ nhớ được phân bổ động. Giải phóng bộ nhớ được phân bổ thông qua malloc()là một vấn đề đau đầu và nếu bằng cách nào đó bỏ lỡ dẫn đến tất cả các loại vấn đề bộ nhớ.

Tại sao việc sử dụng alloca()không được khuyến khích mặc dù các tính năng trên?


40
Chỉ là một ghi chú nhanh. Mặc dù chức năng này có thể được tìm thấy trong hầu hết các trình biên dịch, nhưng tiêu chuẩn ANSI-C không yêu cầu và do đó có thể hạn chế tính di động. Một điều nữa là, bạn không được! free () con trỏ bạn nhận được và nó tự động được giải phóng sau khi bạn thoát khỏi hàm.
merkuro

9
Ngoài ra, một hàm với alloca () sẽ không được nội tuyến nếu được khai báo như vậy.
Justicle

2
@Justicle, bạn có thể cung cấp một số lời giải thích? Tôi rất tò mò về những gì đằng sau hành vi này
Migajek

47
Quên tất cả những ồn ào về tính di động, không cần gọi free(đó rõ ràng là một lợi thế), không có khả năng nội tuyến (rõ ràng phân bổ heap nặng hơn rất nhiều) và vv Lý do duy nhất để tránh allocalà cho kích thước lớn. Đó là, lãng phí hàng tấn bộ nhớ ngăn xếp không phải là một ý tưởng tốt, cộng với bạn có cơ hội tràn ngăn xếp. Nếu đây là trường hợp - hãy xem xét sử dụng malloca/freea
valdo

5
Một khía cạnh tích cực khác allocalà ngăn xếp không thể bị phân mảnh như đống. Điều này có thể hữu ích cho các ứng dụng kiểu chạy mãi mãi trong thời gian thực hoặc thậm chí các ứng dụng quan trọng về an toàn, vì WCRU sau đó có thể được phân tích tĩnh mà không cần dùng đến các nhóm bộ nhớ tùy chỉnh với các vấn đề của riêng họ (không có địa phương tạm thời, tài nguyên tối ưu phụ sử dụng).
Andreas

Câu trả lời:


245

Câu trả lời là có ngay trong mantrang (ít nhất là trên Linux ):

TRẢ LẠI GIÁ TRỊ Hàm alloca () trả về một con trỏ đến đầu của không gian được phân bổ. Nếu phân bổ gây ra tràn ngăn xếp, hành vi chương trình không được xác định.

Điều đó không phải để nói rằng nó không bao giờ nên được sử dụng. Một trong những dự án OSS mà tôi làm việc sử dụng nó rộng rãi và miễn là bạn không lạm dụng nó ( alloca'giá trị lớn), thì tốt thôi. mallocThay vào đó, khi bạn vượt qua mốc "vài trăm byte", thay vào đó là thời gian sử dụng và bạn bè. Bạn vẫn có thể gặp thất bại trong phân bổ, nhưng ít nhất bạn sẽ có một số dấu hiệu cho thấy sự thất bại thay vì chỉ thổi tung ngăn xếp.


35
Vì vậy, thực sự không có vấn đề gì với nó mà bạn cũng sẽ không có khi khai báo mảng lớn?
TED

88
@Sean: Có, rủi ro tràn stack là lý do được đưa ra, nhưng lý do đó hơi ngớ ngẩn. Thứ nhất bởi vì (như Vaibhav nói) các mảng cục bộ lớn gây ra chính xác cùng một vấn đề, nhưng gần như không bị phỉ báng. Ngoài ra, đệ quy có thể dễ dàng thổi tung stack. Xin lỗi nhưng tôi đang hy vọng bạn phản đối ý tưởng phổ biến rằng lý do được đưa ra trong trang người đàn ông là hợp lý.
j_random_hacker

49
Quan điểm của tôi là sự biện minh được đưa ra trong trang man không có ý nghĩa gì, vì alloca () chính xác là "xấu" như những thứ khác (mảng cục bộ hoặc hàm đệ quy) được coi là kosher.
j_random_hacker

39
@ninjalj: Không phải bởi các lập trình viên C / C ++ có kinh nghiệm cao, nhưng tôi nghĩ nhiều người sợ alloca()không có cùng nỗi sợ mảng địa phương hoặc đệ quy (thực tế nhiều người sẽ hét lên alloca()sẽ khen ngợi đệ quy vì nó "có vẻ thanh lịch") . Tôi đồng ý với lời khuyên của Shaun ("alloca () tốt cho việc phân bổ nhỏ") nhưng tôi không đồng ý với suy nghĩ rằng alloca () là ác độc nhất trong số 3 - chúng đều nguy hiểm như nhau!
j_random_hacker

35
Lưu ý: Với chiến lược phân bổ bộ nhớ "lạc quan" của Linux, rất có thể bạn sẽ không nhận được bất kỳ dấu hiệu nào cho thấy lỗi hết heap ... thay vào đó malloc () sẽ trả về cho bạn một con trỏ không phải là NULL, và sau đó khi bạn cố gắng thực sự truy cập vào không gian địa chỉ mà nó trỏ đến, quá trình của bạn (hoặc một số quy trình khác, không thể đoán trước) sẽ bị giết bởi kẻ giết người OOM. Tất nhiên đây là "tính năng" của Linux chứ không phải là vấn đề C / C ++, nhưng đó là điều cần lưu ý khi tranh luận liệu alloca () hay malloc () có "an toàn hơn" hay không. :)
Jeremy Friesner

209

Một trong những lỗi đáng nhớ nhất mà tôi gặp phải là do một hàm nội tuyến được sử dụng alloca. Nó biểu hiện như một tràn ngăn xếp (vì nó phân bổ trên ngăn xếp) tại các điểm ngẫu nhiên của việc thực hiện chương trình.

Trong tệp tiêu đề:

void DoSomething() {
   wchar_t* pStr = alloca(100);
   //......
}

Trong tệp thực hiện:

void Process() {
   for (i = 0; i < 1000000; i++) {
     DoSomething();
   }
}

Vì vậy, những gì đã xảy ra là trình biên dịch DoSomethinghàm nội tuyến và tất cả các phân bổ ngăn xếp đã xảy ra bên trong Process()chức năng và do đó thổi tung ngăn xếp lên. Để bảo vệ tôi (và tôi không phải là người phát hiện ra vấn đề; tôi đã phải khóc và nói với một trong những nhà phát triển cấp cao khi tôi không thể sửa nó), đó không phải allocalà một trong những chuyển đổi chuỗi ATL macro.

Vì vậy, bài học là - không sử dụng allocatrong các chức năng mà bạn nghĩ có thể được nội tuyến.


91
Hấp dẫn. Nhưng điều đó có đủ điều kiện là một lỗi biên dịch không? Rốt cuộc, nội tuyến đã thay đổi hành vi của mã (nó trì hoãn việc giải phóng không gian được phân bổ bằng alloca).
sleske

60
Rõ ràng, ít nhất GCC sẽ tính đến điều này: "Lưu ý rằng một số cách sử dụng nhất định trong định nghĩa hàm có thể khiến nó không phù hợp để thay thế nội tuyến. Trong số các cách sử dụng này là: sử dụng varargs, sử dụng alloca, [...]". gcc.gnu.org/onlinesocs/gcc/Inline.html
sleske

137
Trình biên dịch nào bạn đã hút thuốc?
Thomas Eding

22
Điều tôi không hiểu là tại sao trình biên dịch không sử dụng tốt phạm vi để xác định rằng các cấp phát trong subscope ít nhiều được "giải phóng": con trỏ ngăn xếp có thể quay trở lại điểm của nó trước khi vào phạm vi, giống như những gì được thực hiện khi trở về từ chức năng (không thể sao?)
moala

7
Tôi đã từ chối, nhưng câu trả lời được viết rất tốt: Tôi đồng ý với những người khác rằng bạn đang mắc lỗi alloca vì rõ ràng đó là lỗi trình biên dịch . Trình biên dịch đã đưa ra một giả định bị lỗi trong một tối ưu hóa mà nó không nên thực hiện. Làm việc xung quanh một lỗi trình biên dịch là tốt, nhưng tôi sẽ không lỗi gì ngoài trình biên dịch.
Evan Carroll

75

Câu hỏi cũ nhưng không ai đề cập rằng nó nên được thay thế bằng các mảng có chiều dài thay đổi.

char arr[size];

thay vì

char *arr=alloca(size);

Nó nằm trong C99 tiêu chuẩn và tồn tại dưới dạng phần mở rộng trình biên dịch trong nhiều trình biên dịch.


5
Nó được đề cập bởi Jonathan Leffler về một bình luận cho câu trả lời của Arthur Ulfeldt.
ninjalj

2
Thật vậy, nhưng nó cũng cho thấy nó dễ bị bỏ qua như thế nào, vì tôi đã không nhìn thấy nó mặc dù đã đọc tất cả các phản hồi trước khi đăng.
Patrick Schlüter

6
Một lưu ý - đó là các mảng có chiều dài thay đổi, không phải là mảng động. Cái sau có thể thay đổi kích thước và thường được thực hiện trên heap.
Tim Čas

1
Visual Studio 2015 biên dịch một số C ++ có cùng một vấn đề.
ahcox

2
Linus Torvalds không thích VLAs trong nhân Linux . Kể từ phiên bản 4.20 Linux sẽ gần như không có VLA.
Cristian Ciupitu

60

alloca () rất hữu ích nếu bạn không thể sử dụng biến cục bộ tiêu chuẩn vì kích thước của nó sẽ cần được xác định khi chạy và bạn hoàn toàn có thể đảm bảo rằng con trỏ bạn nhận được từ alloca () sẽ KHÔNG BAO GIỜ được sử dụng sau khi hàm này trả về .

Bạn có thể khá an toàn nếu bạn

  • không trả về con trỏ, hoặc bất cứ thứ gì có chứa nó.
  • không lưu trữ con trỏ trong bất kỳ cấu trúc nào được phân bổ trên heap
  • không để bất kỳ chủ đề khác sử dụng con trỏ

Mối nguy hiểm thực sự đến từ cơ hội người khác sẽ vi phạm những điều kiện này sau đó. Với ý nghĩ đó, thật tuyệt vời khi chuyển bộ đệm cho các chức năng định dạng văn bản vào chúng :)


12
Tính năng VLA (mảng chiều dài thay đổi) của C99 hỗ trợ các biến cục bộ có kích thước động mà không yêu cầu rõ ràng alloca () được sử dụng.
Jonathan Leffler

2
gọn gàng! thấy biết thêm trong phần '3.4 Variable Length dãy' programmersheaven.com/2/Pointers-and-Arrays-page-2
Arthur Ulfeldt

1
Nhưng điều đó không khác với việc xử lý với các con trỏ đến các biến cục bộ. Họ cũng có thể bị lừa xung quanh ...
glglgl

2
@Jonathan Leffler một điều bạn có thể làm với alloca nhưng bạn không thể làm với VLA là sử dụng từ khóa hạn chế với họ. Giống như thế này: float * hạn chế heavy_use_arr = alloca (sizeof (float) * size); thay vì float heavy_use_arr [size]. Nó có thể giúp một số trình biên dịch (gcc 4.8 trong trường hợp của tôi) để tối ưu hóa lắp ráp ngay cả khi kích thước là hằng số biên dịch. Xem câu hỏi của tôi về nó: stackoverflow.com/questions/19026643/USE-restrict-with-arrays
Piotr Lopusiewicz

@JonathanLeffler Một VLA là cục bộ của khối chứa nó. Mặt khác, alloca()phân bổ bộ nhớ kéo dài cho đến khi kết thúc chức năng. Điều này có nghĩa là dường như không có bản dịch thuận tiện, đơn giản sang VLA f() { char *p; if (c) { /* compute x */ p = alloca(x); } else { p = 0; } /* use p */ }. Nếu bạn nghĩ rằng có thể tự động dịch các allocacách sử dụng sang sử dụng VLA nhưng yêu cầu nhiều hơn một nhận xét để mô tả cách thức, tôi có thể đặt câu hỏi này.
Pascal Cuoq

40

Như đã lưu ý trong bài đăng nhóm tin này , có một vài lý do tại sao việc sử dụng allocacó thể được coi là khó khăn và nguy hiểm:

  • Không phải tất cả các trình biên dịch hỗ trợ alloca.
  • Một số trình biên dịch diễn giải hành vi dự định allocakhác nhau, do đó tính di động không được đảm bảo ngay cả giữa các trình biên dịch hỗ trợ nó.
  • Một số triển khai là lỗi.

24
Một điều tôi thấy được đề cập trên liên kết không có ở nơi nào khác trên trang này là một hàm sử dụng alloca()yêu cầu các thanh ghi riêng biệt để giữ con trỏ ngăn xếp và con trỏ khung. Trên CPU x86> = 386, con trỏ ngăn xếp ESPcó thể được sử dụng cho cả hai, giải phóng EBP- trừ khi alloca()được sử dụng.
j_random_hacker

10
Một điểm hay khác trên trang đó là trừ khi trình tạo mã của trình biên dịch xử lý nó như một trường hợp đặc biệt, f(42, alloca(10), 43);có thể bị sập do khả năng con trỏ ngăn xếp được điều chỉnh alloca() sau khi ít nhất một trong các đối số được đẩy vào nó.
j_random_hacker

3
Bài đăng được liên kết dường như được viết bởi John Levine-- anh chàng đã viết "Trình liên kết và trình tải", tôi sẽ tin bất cứ điều gì anh ta nói.
dùng318904

3
Bài đăng được liên kết là một bài trả lời cho một bài đăng của John Levine.
A. Wilcox

6
Hãy nhớ rằng, rất nhiều thứ đã thay đổi kể từ năm 1991. Tất cả các trình biên dịch C hiện đại (thậm chí trong năm 2009) phải xử lý alloca như một trường hợp đặc biệt; đó là một nội tại chứ không phải là một chức năng thông thường và thậm chí có thể không gọi một chức năng. Vì vậy, vấn đề alloca-in-tham số (phát sinh trong K & R C từ những năm 1970) không phải là vấn đề bây giờ. Chi tiết hơn trong một bình luận tôi đã đưa ra về câu trả lời của Tony D
greggo

26

Một vấn đề là nó không chuẩn, mặc dù nó được hỗ trợ rộng rãi. Những thứ khác như nhau, tôi luôn sử dụng một hàm tiêu chuẩn hơn là một phần mở rộng trình biên dịch chung.


21

sử dụng alloca vẫn không được khuyến khích, tại sao?

Tôi không nhận thấy sự đồng thuận như vậy. Rất nhiều ưu điểm mạnh mẽ; một vài nhược điểm:

  • C99 cung cấp các mảng có độ dài thay đổi, thường được sử dụng tốt hơn vì ký hiệu phù hợp hơn với các mảng có độ dài cố định và tổng thể trực quan
  • nhiều hệ thống có ít bộ nhớ / không gian địa chỉ tổng thể có sẵn cho ngăn xếp hơn so với heap, điều này làm cho chương trình dễ bị cạn kiệt bộ nhớ hơn (thông qua tràn ngăn xếp): điều này có thể được coi là một điều tốt hoặc xấu - một trong số các lý do ngăn xếp không tự động phát triển theo cách heap là để ngăn các chương trình ngoài tầm kiểm soát có tác động bất lợi đến toàn bộ máy
  • khi được sử dụng trong phạm vi cục bộ hơn (như vòng lặp whilehoặc for) hoặc trong một số phạm vi, bộ nhớ sẽ tích lũy trên mỗi lần lặp / phạm vi và không được giải phóng cho đến khi hàm thoát: điều này tương phản với các biến thông thường được xác định trong phạm vi của cấu trúc điều khiển (ví dụ for {int i = 0; i < 2; ++i) { X }sẽ tích lũy allocabộ nhớ -ed được yêu cầu tại X, nhưng bộ nhớ cho một mảng có kích thước cố định sẽ được tái chế mỗi lần lặp).
  • trình biên dịch hiện đại thường không có inlinechức năng gọi alloca, nhưng nếu bạn ép buộc thì điều đó allocasẽ xảy ra trong ngữ cảnh của người gọi (tức là ngăn xếp sẽ không được giải phóng cho đến khi người gọi quay lại)
  • một thời gian dài trước đây đã allocachuyển từ tính năng không hack / hack sang tiện ích mở rộng Chuẩn hóa, nhưng một số nhận thức tiêu cực có thể vẫn tồn tại
  • thời gian tồn tại bị ràng buộc với phạm vi chức năng, có thể phù hợp hoặc không phù hợp với lập trình viên hơn là mallockiểm soát rõ ràng
  • phải sử dụng mallockhuyến khích suy nghĩ về việc giải quyết - nếu được quản lý thông qua chức năng bao bọc (ví dụ WonderfulObject_DestructorFree(ptr)), thì chức năng này cung cấp một điểm để thực hiện các thao tác dọn dẹp (như đóng mô tả tệp, giải phóng con trỏ nội bộ hoặc thực hiện ghi nhật ký) mà không thay đổi rõ ràng cho khách hàng mã: đôi khi nó là một mô hình tốt để áp dụng nhất quán
    • trong kiểu lập trình giả OO này, thật tự nhiên muốn có một cái gì đó giống như WonderfulObject* p = WonderfulObject_AllocConstructor();- điều đó có thể xảy ra khi "hàm tạo" là một mallocbộ nhớ trả về hàm (vì bộ nhớ vẫn được phân bổ sau khi hàm trả về giá trị được lưu trữ p), nhưng không nếu "hàm tạo" sử dụngalloca
      • một phiên bản vĩ mô WonderfulObject_AllocConstructorcó thể đạt được điều này, nhưng "macro là xấu xa" ở chỗ chúng có thể xung đột với nhau và mã phi vĩ mô và tạo ra sự thay thế ngoài ý muốn và hậu quả là các vấn đề khó chẩn đoán
    • Các freehoạt động bị thiếu có thể được phát hiện bởi ValGrind, Purify, v.v. nhưng các cuộc gọi "hủy diệt" bị thiếu luôn luôn không thể được phát hiện - một lợi ích rất khó khăn trong việc thực thi mục đích sử dụng; một số alloca()triển khai (chẳng hạn như GCC) sử dụng macro được nội tuyến cho alloca(), vì vậy việc thay thế thời gian chạy của thư viện chẩn đoán sử dụng bộ nhớ không thể thực hiện theo cách malloc/ realloc/ free(ví dụ: hàng rào điện)
  • một số triển khai có các vấn đề tinh tế: ví dụ: từ trang web Linux:

    Trên nhiều hệ thống, alloca () không thể được sử dụng trong danh sách các đối số của lệnh gọi hàm, bởi vì không gian ngăn xếp dành cho alloca () sẽ xuất hiện trên ngăn xếp ở giữa không gian cho các đối số hàm.


Tôi biết câu hỏi này được gắn thẻ C, nhưng với tư cách là một lập trình viên C ++, tôi nghĩ rằng tôi đã sử dụng C ++ để minh họa tiện ích tiềm năng của alloca: mã bên dưới (và ở đây tại ideone ) tạo ra một vectơ theo dõi các loại đa hình có kích thước khác nhau được phân bổ (với trọn đời gắn liền với chức năng trả về) chứ không phải phân bổ heap.

#include <alloca.h>
#include <iostream>
#include <vector>

struct Base
{
    virtual ~Base() { }
    virtual int to_int() const = 0;
};

struct Integer : Base
{
    Integer(int n) : n_(n) { }
    int to_int() const { return n_; }
    int n_;
};

struct Double : Base
{
    Double(double n) : n_(n) { }
    int to_int() const { return -n_; }
    double n_;
};

inline Base* factory(double d) __attribute__((always_inline));

inline Base* factory(double d)
{
    if ((double)(int)d != d)
        return new (alloca(sizeof(Double))) Double(d);
    else
        return new (alloca(sizeof(Integer))) Integer(d);
}

int main()
{
    std::vector<Base*> numbers;
    numbers.push_back(factory(29.3));
    numbers.push_back(factory(29));
    numbers.push_back(factory(7.1));
    numbers.push_back(factory(2));
    numbers.push_back(factory(231.0));
    for (std::vector<Base*>::const_iterator i = numbers.begin();
         i != numbers.end(); ++i)
    {
        std::cout << *i << ' ' << (*i)->to_int() << '\n';
        (*i)->~Base();   // optionally / else Undefined Behaviour iff the
                         // program depends on side effects of destructor
    }
}

không +1 vì cách xử lý cá nhân đơn giản, một số loại :-(
einpoklum

@einpoklum: thật tuyệt vời ... cảm ơn.
Tony Delroy

1
Hãy để tôi nói lại: Đây là một câu trả lời rất tốt. Cho đến khi tôi nghĩ rằng bạn đang đề xuất rằng mọi người sử dụng một kiểu mẫu đối lập.
einpoklum

Nhận xét từ trang man linux rất cũ và, tôi khá chắc chắn, lỗi thời. Tất cả các trình biên dịch hiện đại đều biết alloca () là gì và sẽ không vấp phải dây giày của họ như thế. Trong K & R C cũ, (1) tất cả các hàm được sử dụng con trỏ khung (2) Tất cả các lệnh gọi hàm là {push args trên stack} {call func} {add # n, sp}. alloca là một hàm lib sẽ tăng số lượng lên, trình biên dịch thậm chí không biết về điều đó xảy ra. (1) và (2) không còn đúng nữa nên alloca không thể hoạt động theo cách đó (bây giờ nó là nội tại). Ở C cũ, việc gọi alloca vào giữa các cuộc tranh luận rõ ràng cũng sẽ phá vỡ những giả định đó.
greggo

4
Về ví dụ, tôi thường quan tâm đến một cái gì đó đòi hỏi always_inline để tránh hỏng bộ nhớ ....
greggo

14

Tất cả các câu trả lời khác là chính xác. Tuy nhiên, nếu thứ bạn muốn phân bổ sử dụng alloca()là khá nhỏ, tôi nghĩ rằng đó là một kỹ thuật tốt nhanh hơn và thuận tiện hơn so với sử dụng malloc()hoặc cách khác.

Nói cách khác, alloca( 0x00ffffff )nguy hiểm và có khả năng gây tràn, chính xác là nhiều như vậy char hugeArray[ 0x00ffffff ];. Hãy thận trọng và hợp lý và bạn sẽ ổn thôi.


12

Rất nhiều câu trả lời thú vị cho câu hỏi "cũ" này, thậm chí một số câu trả lời tương đối mới, nhưng tôi không tìm thấy câu trả lời nào ....

Khi được sử dụng đúng cách và cẩn thận, việc sử dụng nhất quán alloca() (có thể là toàn ứng dụng) để xử lý phân bổ độ dài biến nhỏ (hoặc C99 VLA, nếu có) có thể dẫn đến tăng trưởng ngăn xếp tổng thể thấp hơn so với triển khai tương đương bằng cách sử dụng các mảng cục bộ quá khổ có độ dài cố định . Vì vậy, alloca()có thể tốt cho ngăn xếp của bạn nếu bạn sử dụng nó một cách cẩn thận.

Tôi tìm thấy trích dẫn đó trong .... OK, tôi đã thực hiện trích dẫn đó. Nhưng thực sự, hãy nghĩ về nó ....

@j_random_hacker rất đúng trong các nhận xét của mình theo các câu trả lời khác: Tránh sử dụng alloca()các mảng cục bộ quá khổ không làm cho chương trình của bạn an toàn hơn khỏi tràn ngăn xếp (trừ khi trình biên dịch của bạn đủ cũ để cho phép nội tuyến sử dụng alloca()trong trường hợp bạn nên nâng cấp hoặc trừ khi bạn sử dụng alloca()các vòng lặp bên trong, trong trường hợp đó bạn không nên ... không sử dụngalloca() các vòng lặp bên trong).

Tôi đã làm việc trên môi trường máy tính để bàn / máy chủ và các hệ thống nhúng. Rất nhiều hệ thống nhúng hoàn toàn không sử dụng một đống (thậm chí chúng không liên kết hỗ trợ cho nó), vì những lý do bao gồm nhận thức rằng bộ nhớ được phân bổ động là xấu do rủi ro rò rỉ bộ nhớ trên một ứng dụng không bao giờ từng khởi động lại trong nhiều năm, hoặc bằng chứng hợp lý hơn rằng bộ nhớ động là nguy hiểm vì không thể biết chắc chắn rằng một ứng dụng sẽ không bao giờ phân mảnh đến mức cạn kiệt bộ nhớ sai. Vì vậy, các lập trình viên nhúng được để lại với một vài lựa chọn thay thế.

alloca() (hoặc VLAs) có thể chỉ là công cụ phù hợp cho công việc.

Tôi đã thấy thời gian và thời gian một lần nữa trong đó một lập trình viên tạo ra một bộ đệm được cấp phát ngăn xếp "đủ lớn để xử lý mọi trường hợp có thể". Trong một cây gọi được lồng sâu, việc sử dụng lặp lại mẫu đó (chống?) Dẫn đến việc sử dụng ngăn xếp phóng đại. (Hãy tưởng tượng cây gọi sâu 20 cấp, trong đó ở mỗi cấp vì các lý do khác nhau, hàm sẽ phân bổ quá mức một bộ đệm 1024 byte "chỉ để an toàn" khi nói chung nó sẽ chỉ sử dụng 16 hoặc ít hơn trong số chúng và chỉ trong rất ít trường hợp hiếm hoi có thể sử dụng nhiều hơn.) Một cách khác là sử dụngalloca()hoặc VLAs và chỉ phân bổ không gian ngăn xếp nhiều như chức năng của bạn cần, để tránh gánh nặng không cần thiết cho ngăn xếp. Hy vọng rằng khi một hàm trong cây cuộc gọi cần phân bổ lớn hơn bình thường, các hàm khác trong cây cuộc gọi vẫn sử dụng phân bổ nhỏ thông thường của chúng và việc sử dụng ngăn xếp ứng dụng tổng thể ít hơn đáng kể so với khi mọi hàm được phân bổ quá mức một bộ đệm cục bộ .

Nhưng nếu bạn chọn sử dụng alloca()...

Dựa trên các câu trả lời khác trên trang này, có vẻ như các VLA phải an toàn (chúng không phân bổ ngăn xếp gộp nếu được gọi từ trong vòng lặp), nhưng nếu bạn đang sử dụng alloca(), hãy cẩn thận không sử dụng nó trong vòng lặp và thực hiện chắc chắn rằng chức năng của bạn không thể được nội tuyến nếu có bất kỳ cơ hội nào nó có thể được gọi trong vòng lặp của chức năng khác.


Tôi đồng ý với điểm này. Điều nguy hiểm alloca()là đúng, nhưng điều tương tự cũng có thể nói đối với rò rỉ bộ nhớ với malloc()(tại sao không sử dụng GC sau đó? Người ta có thể tranh luận). alloca()khi được sử dụng cẩn thận có thể thực sự hữu ích để giảm kích thước ngăn xếp.
Felipe Tonello

Một lý do chính đáng khác để không sử dụng bộ nhớ động, đặc biệt là trong nhúng: nó phức tạp hơn việc dính vào ngăn xếp. Sử dụng bộ nhớ động đòi hỏi các quy trình và cấu trúc dữ liệu đặc biệt, trong khi trên ngăn xếp, việc đơn giản hóa / trừ đi một số lượng cao hơn từ stackpulum.
tehftw

Sidenote: Ví dụ "sử dụng bộ đệm cố định [MAX_SIZE]" nêu bật lý do tại sao chính sách bộ nhớ quá mức hoạt động tốt như vậy. Các chương trình phân bổ bộ nhớ mà chúng có thể không bao giờ chạm vào ngoại trừ ở giới hạn độ dài bộ đệm của chúng. Vì vậy, thật tốt khi Linux (và các hệ điều hành khác) không thực sự chỉ định một trang bộ nhớ cho đến khi nó được sử dụng lần đầu tiên (trái ngược với malloc'd). Nếu bộ đệm lớn hơn một trang, chương trình chỉ có thể sử dụng trang đầu tiên và không lãng phí phần còn lại của bộ nhớ vật lý.
Chuyến đi Katastic

@KatasticVoyage Trừ khi MAX_SIZE lớn hơn (hoặc ít nhất bằng) kích thước trang kích thước bộ nhớ ảo của hệ thống, đối số của bạn không giữ được nước. Ngoài ra, trên các hệ thống nhúng không có bộ nhớ ảo (nhiều MCU nhúng không có MMU), chính sách bộ nhớ quá mức có thể tốt từ quan điểm "đảm bảo chương trình của bạn sẽ chạy trong mọi tình huống", nhưng đảm bảo rằng giá của kích thước ngăn xếp của bạn phải được phân bổ tương tự để hỗ trợ chính sách bộ nhớ quá mức đó. Trên một số hệ thống nhúng, đó là mức giá mà một số nhà sản xuất sản phẩm giá rẻ không sẵn sàng trả.
phonetagger

11

Mọi người đã chỉ ra điều lớn là hành vi không xác định tiềm năng từ tràn ngăn xếp nhưng tôi nên đề cập rằng môi trường Windows có một cơ chế tuyệt vời để nắm bắt điều này bằng cách sử dụng các ngoại lệ có cấu trúc (SEH) và các trang bảo vệ. Vì ngăn xếp chỉ phát triển khi cần thiết, các trang bảo vệ này nằm trong các khu vực không được phân bổ. Nếu bạn phân bổ vào chúng (bằng cách tràn ngăn xếp), một ngoại lệ sẽ được ném ra.

Bạn có thể bắt ngoại lệ SEH này và gọi _resetstkoflw để đặt lại ngăn xếp và tiếp tục theo cách vui vẻ của bạn. Nó không lý tưởng nhưng đó là một cơ chế khác để ít nhất biết có gì đó không ổn khi thứ đó chạm vào quạt. * nix có thể có một cái gì đó tương tự mà tôi không biết.

Tôi khuyên bạn nên giới hạn kích thước phân bổ tối đa của mình bằng cách gói alloca và theo dõi nội bộ. Nếu bạn thực sự khó tính về điều đó, bạn có thể ném một số phạm vi phạm vi lên đầu chức năng của mình để theo dõi bất kỳ phân bổ alloca nào trong phạm vi chức năng và kiểm tra mức độ tối đa cho phép đối với dự án của bạn.

Ngoài ra, ngoài việc không cho phép rò rỉ bộ nhớ, alloca không gây ra sự phân mảnh bộ nhớ, điều này khá quan trọng. Tôi không nghĩ alloca là thực hành tồi nếu bạn sử dụng nó một cách thông minh, điều này về cơ bản là đúng cho mọi thứ. :-)


Vấn đề là, điều đó alloca()có thể đòi hỏi rất nhiều không gian, mà stackpulum hạ cánh trong đống. Cùng với đó, kẻ tấn công có thể kiểm soát kích thước alloca()và dữ liệu đi vào bộ đệm đó có thể ghi đè lên vùng heap (rất tệ).
12431234123412341234123

SEH là một thứ chỉ dành cho Windows. Thật tuyệt nếu bạn chỉ quan tâm đến mã của mình chạy trên Windows, nhưng nếu mã của bạn cần phải đa nền tảng (hoặc nếu bạn đang viết mã chỉ chạy trên nền tảng không phải Windows), thì bạn không thể dựa vào việc có SEH.
George

10

alloca () là tốt đẹp và hiệu quả ... nhưng nó cũng bị phá vỡ sâu sắc.

  • hành vi phạm vi bị hỏng (phạm vi chức năng thay vì phạm vi khối)
  • sử dụng không phù hợp với con trỏ malloc ( alloca () -ted không nên được giải phóng, do đó, bạn phải theo dõi nơi con trỏ của bạn đến từ free () chỉ những người bạn có với malloc () )
  • hành vi xấu khi bạn cũng sử dụng nội tuyến (phạm vi đôi khi đi đến chức năng người gọi tùy thuộc vào việc callee có được nội tuyến hay không).
  • không kiểm tra ranh giới ngăn xếp
  • hành vi không xác định trong trường hợp thất bại (không trả về NULL như malloc ... và thất bại có nghĩa là gì vì dù sao nó cũng không kiểm tra ranh giới ngăn xếp ...)
  • không phải tiêu chuẩn ansi

Trong hầu hết các trường hợp, bạn có thể thay thế nó bằng các biến cục bộ và kích thước chính. Nếu nó được sử dụng cho các vật thể lớn, đặt chúng trên đống thường là một ý tưởng an toàn hơn.

Nếu bạn thực sự cần nó C, bạn có thể sử dụng VLA (không có vla trong C ++, quá tệ). Chúng tốt hơn nhiều so với alloca () về hành vi phạm vi và tính nhất quán. Theo tôi thấy, VLA là một loại alloca () được làm đúng.

Tất nhiên, một cấu trúc hoặc mảng cục bộ sử dụng một phần lớn không gian cần thiết vẫn tốt hơn và nếu bạn không có phân bổ heap lớn như vậy bằng cách sử dụng malloc () có lẽ là lành mạnh. Tôi thấy không có trường hợp sử dụng lành mạnh nào mà bạn thực sự cần alloca () hoặc VLA.


Tôi không thấy lý do cho downvote (nhân tiện, không có bất kỳ bình luận nào)
gd1

Chỉ tên có phạm vi. allocakhông tạo tên, chỉ phạm vi bộ nhớ, có thời gian tồn tại .
tò mò

@cquilguy: bạn chỉ đang chơi chữ. Đối với các biến tự động, tôi cũng có thể nói về tuổi thọ của bộ nhớ cơ bản vì nó phù hợp với phạm vi của tên. Dù sao, rắc rối không phải là cách chúng ta gọi nó, mà là sự bất ổn của thời gian sống / phạm vi của bộ nhớ được trả lại bởi alloca và hành vi đặc biệt.
kriss

2
Tôi ước alloca đã có một "freea" tương ứng, với một đặc điểm kỹ thuật gọi là "freea" sẽ hoàn tác các tác động của "alloca" đã tạo ra đối tượng và tất cả những thứ tiếp theo, và yêu cầu lưu trữ 'alloca'ed trong một fucntion phải cũng được 'giải phóng trong đó. Điều đó sẽ giúp cho gần như tất cả các triển khai hỗ trợ alloca / freea theo cách tương thích, sẽ giảm bớt các vấn đề nội tuyến và nói chung làm mọi thứ sạch hơn rất nhiều.
supercat

2
@supercat - Mình cũng ước như vậy. Vì lý do đó (và nhiều hơn nữa), tôi sử dụng một lớp trừu tượng (chủ yếu là các macro và các hàm inline) vì vậy mà tôi không bao giờ gọi allocahoặc mallochoặc freetrực tiếp. Tôi nói những điều như {stack|heap}_alloc_{bytes,items,struct,varstruct}{stack|heap}_dealloc. Vì vậy, heap_deallocchỉ cần gọi freestack_dealloclà không có. Bằng cách này, phân bổ ngăn xếp có thể dễ dàng bị hạ cấp thành phân bổ đống, và ý định cũng rõ ràng hơn.
Todd Lehman

9

Đây là lý do tại sao:

char x;
char *y=malloc(1);
char *z=alloca(&x-y);
*z = 1;

Không phải ai cũng sẽ viết mã này, nhưng đối số kích thước mà bạn chuyển đến allocagần như chắc chắn đến từ một loại đầu vào nào đó, có thể nhắm mục tiêu xấu để đưa chương trình của bạn đến allocamột cái gì đó lớn như thế. Rốt cuộc, nếu kích thước không dựa trên đầu vào hoặc không có khả năng lớn, tại sao bạn không khai báo một bộ đệm cục bộ có kích thước cố định?

Hầu như tất cả các mã sử dụng allocavà / hoặc C99 vlas đều có lỗi nghiêm trọng sẽ dẫn đến sự cố (nếu bạn may mắn) hoặc thỏa hiệp đặc quyền (nếu bạn không may mắn như vậy).


1
Thế giới có thể không bao giờ biết. :( Điều đó nói rằng, tôi hy vọng bạn có thể làm rõ một câu hỏi mà tôi có alloca. Bạn nói rằng gần như tất cả các mã sử dụng nó đều có lỗi, nhưng tôi đã lên kế hoạch sử dụng nó; tôi thường bỏ qua yêu cầu như vậy, nhưng sẽ đến từ bạn tôi sẽ không. Tôi đang viết một máy ảo và tôi muốn phân bổ các biến không thoát khỏi chức năng trên ngăn xếp, thay vì động, vì tốc độ cực lớn. Có một sự thay thế Cách tiếp cận có đặc điểm hiệu suất tương tự? Tôi biết tôi có thể tiến gần với nhóm bộ nhớ, nhưng điều đó vẫn không rẻ. Bạn sẽ làm gì?
GManNickG

7
Biết cái gì cũng nguy hiểm? Điều này: *0 = 9;TUYỆT VỜI !!! Tôi đoán tôi không bao giờ nên sử dụng con trỏ (hoặc ít nhất là loại bỏ chúng). Ơ, đợi đã. Tôi có thể kiểm tra xem nó có null không. Hừm. Tôi đoán tôi cũng có thể kiểm tra kích thước của bộ nhớ mà tôi muốn phân bổ thông qua alloca. Người đàn ông kì dị. Kỳ dị.
Thomas Eding

7
*0=9;không hợp lệ C. Còn về việc kiểm tra kích thước bạn vượt qua alloca, hãy kiểm tra xem nó có gì không? Không có cách nào để biết giới hạn và nếu bạn chỉ muốn kiểm tra nó với kích thước an toàn đã biết cố định nhỏ (ví dụ 8k), bạn cũng có thể chỉ sử dụng một mảng có kích thước cố định trên ngăn xếp.
R .. GitHub DỪNG GIÚP ICE

7
Vấn đề với đối số "kích thước của bạn được biết là đủ nhỏ hoặc phụ thuộc vào đầu vào và do đó có thể lớn tùy ý" vì tôi thấy rằng nó áp dụng mạnh mẽ cho đệ quy. Một thỏa hiệp thực tế (cho cả hai trường hợp) là giả định rằng nếu kích thước bị giới hạn bởi small_constant * log(user_input)thì chúng ta có thể có đủ bộ nhớ.
j_random_hacker

1
Thật vậy, bạn đã xác định MỘT trường hợp trong đó VLA / alloca hữu ích: thuật toán đệ quy trong đó không gian tối đa cần thiết ở bất kỳ khung gọi nào có thể lớn bằng N, nhưng trong đó tổng không gian cần thiết ở tất cả các mức đệ quy là N hoặc một số hàm của N mà không phát triển nhanh chóng.
R .. GitHub DỪNG GIÚP ICE

9

Tôi không nghĩ có ai đã đề cập đến điều này: Việc sử dụng alloca trong một hàm sẽ cản trở hoặc vô hiệu hóa một số tối ưu hóa có thể được áp dụng trong hàm, vì trình biên dịch không thể biết kích thước của khung ngăn xếp của hàm.

Ví dụ, một tối ưu hóa phổ biến của trình biên dịch C là loại bỏ việc sử dụng con trỏ khung trong một hàm, truy cập khung được thực hiện liên quan đến con trỏ ngăn xếp; vì vậy có thêm một đăng ký để sử dụng chung. Nhưng nếu alloca được gọi trong hàm, sự khác biệt giữa sp và fp sẽ không xác định được đối với một phần của hàm, vì vậy việc tối ưu hóa này không thể thực hiện được.

Do tính hiếm khi sử dụng và trạng thái mờ ám của nó như là một chức năng tiêu chuẩn, các nhà thiết kế trình biên dịch hoàn toàn có thể vô hiệu hóa bất kỳ tối ưu hóa nào có thể gây rắc rối với alloca, nếu mất nhiều nỗ lực hơn để làm cho nó hoạt động với alloca.

CẬP NHẬT: Vì các mảng cục bộ có độ dài thay đổi đã được thêm vào C và do các vấn đề tạo mã rất giống với trình biên dịch này là alloca, tôi thấy rằng "sự hiếm khi sử dụng và trạng thái mờ ám" không áp dụng cho cơ chế bên dưới; nhưng tôi vẫn nghi ngờ rằng việc sử dụng alloca hoặc VLA có xu hướng thỏa hiệp việc tạo mã trong một hàm sử dụng chúng. Tôi sẽ hoan nghênh bất kỳ thông tin phản hồi từ các nhà thiết kế trình biên dịch.


1
Mảng độ dài thay đổi không bao giờ được thêm vào C ++.
Nir Friedman

@NirFriedman Thật vậy. Tôi nghĩ rằng có một danh sách tính năng wikipedia dựa trên một đề xuất cũ.
greggo

> Tôi vẫn nghi ngờ rằng việc sử dụng alloca hoặc VLA có xu hướng thỏa hiệp việc tạo mã Tôi sẽ nghĩ rằng việc sử dụng alloca đòi hỏi một con trỏ khung, bởi vì con trỏ ngăn xếp di chuyển theo những cách không rõ ràng trong thời gian biên dịch. alloca có thể được gọi trong một vòng lặp để tiếp tục lấy thêm bộ nhớ ngăn xếp hoặc với kích thước tính toán thời gian chạy, v.v. Nếu có một con trỏ khung, mã được tạo có một tham chiếu ổn định đến các địa phương và con trỏ ngăn xếp có thể làm bất cứ điều gì nó muốn; nó không được sử dụng
Kaz

8

Một cạm bẫy với allocađó là longjmptua lại nó.

Điều đó có nghĩa là, nếu bạn lưu một bối cảnh với setjmp, sau đó allocamột số bộ nhớ, sau đólongjmp đến bối cảnh, bạn có thể mất allocabộ nhớ. Con trỏ ngăn xếp trở lại vị trí cũ và do đó bộ nhớ không còn được bảo lưu; nếu bạn gọi một chức năng hoặc làm một chức năng khác alloca, bạn sẽ ghi đè lên bản gốc alloca.

Để làm rõ, điều tôi đặc biệt đề cập ở đây là một tình huống trong đó longjmpkhông quay trở lại chức năng nơi allocadiễn ra! Thay vào đó, một chức năng lưu bối cảnh với setjmp; sau đó phân bổ bộ nhớ với allocavà cuối cùng là một longjmp diễn ra trong bối cảnh đó. allocaBộ nhớ của chức năng đó không phải là tất cả được giải phóng; chỉ tất cả bộ nhớ mà nó phân bổ kể từ setjmp. Tất nhiên, tôi đang nói về một hành vi được quan sát; không có yêu cầu như vậy được ghi lại trong bất kỳ allocađiều gì tôi biết.

Trọng tâm trong tài liệu thường tập trung vào khái niệm rằng allocabộ nhớ được liên kết với kích hoạt chức năng , không phải với bất kỳ khối nào; rằng nhiều lệnh allocachỉ cần lấy thêm bộ nhớ stack, tất cả được giải phóng khi hàm kết thúc. Không phải vậy; bộ nhớ thực sự được liên kết với bối cảnh thủ tục. Khi bối cảnh được khôi phục với longjmp, allocatrạng thái trước cũng vậy . Đó là hậu quả của chính thanh ghi con trỏ ngăn xếp được sử dụng để phân bổ, và cũng (nhất thiết) được lưu và khôi phục trong jmp_buf.

Ngẫu nhiên, điều này, nếu nó hoạt động theo cách đó, cung cấp một cơ chế hợp lý để giải phóng bộ nhớ có chủ ý được phân bổ alloca.

Tôi đã chạy vào đây là nguyên nhân gốc của một lỗi.


1
Đó là những gì nó phải làm mặc dù - longjmpquay lại và làm cho nó để chương trình quên đi mọi thứ xảy ra trong ngăn xếp: tất cả các biến, hàm gọi, v.v. Và allocagiống như một mảng trên ngăn xếp, vì vậy nó dự kiến ​​chúng sẽ bị ghi đè như mọi thứ khác trên stack.
tehftw

1
man allocađã đưa ra câu sau: "Bởi vì không gian được phân bổ bởi alloca () được phân bổ trong khung ngăn xếp, không gian đó sẽ tự động được giải phóng nếu hàm trả về được nhảy qua một lệnh gọi đến longjmp (3) hoặc siglongjmp (3).". Vì vậy, nó được ghi nhận rằng bộ nhớ được phân bổ allocabị ghi đè sau a longjmp.
tehftw

@tehftw Tình huống được mô tả xảy ra mà không có hàm trả về bị nhảy qua longjmp. Hàm mục tiêu chưa trở lại. Nó đã được thực hiện setjmp, allocavà sau đó longjmp. Có longjmpthể tua lại allocatrạng thái trở lại như setjmpthời điểm đó. Điều đó có nghĩa là, con trỏ di chuyển allocabị vấn đề tương tự như một biến cục bộ chưa được đánh dấu volatile!
Kaz

3
Tôi không hiểu tại sao nó lại được coi là bất ngờ. Khi bạn setjmpsau đó alloca, và sau đó longjmp, đó là bình thường allocasẽ được tua lại. Toàn bộ vấn đề longjmplà quay trở lại trạng thái đã được lưu tại setjmp!
tehftw

@tehftw Tôi chưa bao giờ thấy tài liệu tương tác cụ thể này. Do đó, nó không thể dựa vào một trong hai cách, ngoài việc điều tra theo kinh nghiệm với các trình biên dịch.
Kaz

7

Một nơi alloca()đặc biệt nguy hiểm hơn malloc()là kernel - kernel của một hệ điều hành thông thường có một không gian ngăn xếp có kích thước cố định được mã hóa cứng vào một trong các tiêu đề của nó; nó không linh hoạt như ngăn xếp của một ứng dụng. Thực hiện cuộc gọi đến alloca()với kích thước không chính đáng có thể khiến kernel bị sập. Một số trình biên dịch cảnh báo việc sử dụng alloca()(và thậm chí cả VLAs cho vấn đề đó) theo các tùy chọn nhất định phải được bật trong khi biên dịch mã hạt nhân - ở đây, tốt hơn là phân bổ bộ nhớ trong heap không bị cố định bởi giới hạn mã hóa cứng.


7
alloca()không nguy hiểm hơn int foo[bar];nơi barmột số nguyên tùy ý.
Todd Lehman

@ToddLehman Điều đó đúng và vì lý do chính xác đó, chúng tôi đã cấm VLAs trong kernel trong vài năm và đã không có VLA kể từ năm 2018 :-)
Chris Down

6

Nếu bạn vô tình viết vượt quá khối được phân bổ alloca(ví dụ do tràn bộ đệm), thì bạn sẽ ghi đè địa chỉ trả về của hàm, bởi vì khối đó nằm ở "trên" trên ngăn xếp, tức là sau khối được phân bổ của bạn.

khối _alloca trên ngăn xếp

Hậu quả của việc này là hai lần:

  1. Chương trình sẽ bị sập một cách ngoạn mục và không thể biết tại sao hoặc nơi nó bị sập (stack rất có thể sẽ thư giãn đến một địa chỉ ngẫu nhiên do con trỏ khung bị ghi đè).

  2. Nó làm cho bộ đệm tràn nguy hiểm hơn nhiều lần, vì một người dùng độc hại có thể tạo ra một trọng tải đặc biệt sẽ được đưa vào ngăn xếp và do đó cuối cùng có thể được thực thi.

Ngược lại, nếu bạn viết vượt quá một khối trên heap, bạn "chỉ" nhận được heap tham nhũng. Chương trình có thể sẽ chấm dứt bất ngờ nhưng sẽ giải phóng ngăn xếp đúng cách, do đó làm giảm cơ hội thực thi mã độc.


11
Không có gì trong tình huống này khác biệt đáng kể với sự nguy hiểm của bộ đệm - tràn bộ đệm được phân bổ có kích thước cố định. Nguy hiểm này không phải là duy nhất alloca.
phonetagger

2
Dĩ nhiên là không. Nhưng hãy kiểm tra câu hỏi ban đầu. Câu hỏi là: những gì nguy hiểm allocaso với malloc(do đó không phải là bộ đệm có kích thước cố định trên ngăn xếp).
rustyx

Điểm nhỏ, nhưng ngăn xếp trên một số hệ thống phát triển lên (ví dụ bộ vi xử lý PIC 16 bit).
EBlake

5

Đáng buồn là thực sự tuyệt vời alloca()bị thiếu từ tcc gần như tuyệt vời. Gcc nào có alloca().

  1. Nó gieo hạt giống hủy diệt của chính nó. Với lợi nhuận là hàm hủy.

  2. Giống như malloc()nó trả về một con trỏ không hợp lệ khi không thành công, nó sẽ segfault trên các hệ thống hiện đại với MMU (và hy vọng khởi động lại những con trỏ không có).

  3. Không giống như các biến tự động, bạn có thể chỉ định kích thước trong thời gian chạy.

Nó hoạt động tốt với đệ quy. Bạn có thể sử dụng các biến tĩnh để đạt được một cái gì đó tương tự như đệ quy đuôi và chỉ sử dụng một vài biến khác truyền thông tin cho mỗi lần lặp.

Nếu bạn đẩy quá sâu, bạn yên tâm về segfault (nếu bạn có MMU).

Lưu ý rằng malloc()không cung cấp nhiều hơn vì nó trả về NULL (cũng sẽ segfault nếu được gán) khi hệ thống hết bộ nhớ. Tức là tất cả những gì bạn có thể làm là bảo lãnh hoặc chỉ cố gắng chỉ định nó bằng mọi cách.

Để sử dụng malloc()tôi sử dụng toàn cầu và gán cho họ NULL. Nếu con trỏ không phải là NULL, tôi sẽ giải phóng nó trước khi sử dụng malloc().

Bạn cũng có thể dùng realloc() như trường hợp chung nếu muốn sao chép bất kỳ dữ liệu hiện có. Bạn cần kiểm tra con trỏ trước để tìm ra nếu bạn định sao chép hoặc ghép nối sau realloc().

3.2.5.2 Ưu điểm của alloca


4
Trên thực tế, thông số alloca không nói rằng nó trả về một con trỏ không hợp lệ khi bị lỗi (stack stack) nó nói rằng nó có hành vi không xác định ... và đối với malloc, nó nói nó trả về NULL, không phải là một con trỏ không hợp lệ ngẫu nhiên (OK, việc triển khai bộ nhớ lạc quan của Linux làm cho điều đó vô ích).
kriss

@kriss Linux có thể giết chết quá trình của bạn, nhưng ít nhất nó không liên quan đến hành vi không xác định
craig65535

@ craig65535: biểu thức hành vi không xác định thường có nghĩa là hành vi đó không được xác định bởi đặc tả C hoặc C ++. Không phải là ngẫu nhiên hay không ổn định trên bất kỳ hệ điều hành hoặc trình biên dịch cụ thể nào. Do đó, việc kết hợp UB với tên của một hệ điều hành như "Linux" hoặc "Windows" là vô nghĩa. Nó không có gì để làm với nó.
kriss

Tôi đã cố gắng nói rằng malloc trả lại NULL, hoặc trong trường hợp Linux, truy cập bộ nhớ giết quá trình của bạn, thích hợp hơn với hành vi không xác định của alloca. Tôi nghĩ rằng tôi đã đọc sai nhận xét đầu tiên của bạn.
craig65535

3

Các quy trình chỉ có một lượng không gian ngăn xếp có sẵn - ít hơn nhiều so với dung lượng bộ nhớ khả dụng malloc().

Bằng cách sử dụng, alloca()bạn sẽ tăng đáng kể khả năng gặp lỗi Stack Overflow (nếu bạn may mắn hoặc gặp sự cố không thể giải thích nếu bạn không).


Điều đó phụ thuộc rất nhiều vào ứng dụng. Không có gì lạ khi một ứng dụng nhúng bị giới hạn bộ nhớ có kích thước ngăn xếp lớn hơn heap (thậm chí nếu có cả đống).
EBlake

3

Không đẹp lắm, nhưng nếu hiệu suất thực sự quan trọng, bạn có thể sắp xếp trước một số không gian trên ngăn xếp.

Nếu bây giờ bạn đã có kích thước tối đa của bộ nhớ mà bạn cần và bạn muốn tiếp tục kiểm tra tràn, bạn có thể làm một cái gì đó như:

void f()
{
    char array_on_stack[ MAX_BYTES_TO_ALLOCATE ];
    SomeType *p = (SomeType *)array;

    (...)
}

12
Là mảng char được đảm bảo để được căn chỉnh chính xác cho bất kỳ loại dữ liệu? alloca cung cấp lời hứa như vậy.
Juho Östman

@ JuhoÖstman: bạn có thể sử dụng một mảng struct (hoặc bất kỳ loại nào) thay vì char nếu bạn có vấn đề sắp xếp.
kriss

Đó được gọi là Mảng độ dài biến . Nó được hỗ trợ trong C90 trở lên, nhưng không hỗ trợ C ++. Xem Tôi có thể sử dụng Mảng độ dài biến C trong C ++ 03 và C ++ 11 không?
jww

3

Chức năng alloca là tuyệt vời và tất cả những người không tán thành chỉ đơn giản là truyền bá FUD.

void foo()
{
    int x = 50000; 
    char array[x];
    char *parray = (char *)alloca(x);
}

Mảng và parray hoàn toàn giống nhau với những rủi ro CHÍNH XÁC giống nhau. Nói cái này tốt hơn cái khác là lựa chọn cú pháp, không phải là kỹ thuật.

Đối với việc chọn biến ngăn xếp so với biến heap, có rất nhiều lợi thế cho các chương trình chạy dài sử dụng stack over heap cho các biến có vòng đời trong phạm vi. Bạn tránh phân mảnh heap và bạn có thể tránh tăng không gian xử lý của mình với không gian heap không sử dụng (không sử dụng được). Bạn không cần phải làm sạch nó. Bạn có thể kiểm soát phân bổ ngăn xếp trên quy trình.

Tại sao điều này là xấu?


3

Trên thực tế, alloca không được đảm bảo để sử dụng ngăn xếp. Thật vậy, việc triển khai gcc-2,95 của alloca phân bổ bộ nhớ từ heap bằng chính malloc. Ngoài ra, việc triển khai là có lỗi, nó có thể dẫn đến rò rỉ bộ nhớ và một số hành vi không mong muốn nếu bạn gọi nó trong một khối có sử dụng thêm goto. Không, để nói rằng bạn không bao giờ nên sử dụng nó, nhưng đôi khi alloca dẫn đến nhiều chi phí hơn là nó không liên quan.


Nghe có vẻ như gcc-2,95 đã phá vỡ alloca và có lẽ không thể sử dụng an toàn cho các chương trình yêu cầu alloca. Làm thế nào nó có thể dọn sạch bộ nhớ khi longjmpđược sử dụng để từ bỏ các khung đã làm alloca? Khi nào có ai sử dụng gcc 2,95 ngày hôm nay?
Kaz

2

IMHO, alloca được coi là thực hành xấu vì mọi người đều sợ cạn kiệt giới hạn kích thước ngăn xếp.

Tôi đã học được nhiều bằng cách đọc chủ đề này và một số liên kết khác:

Tôi sử dụng alloca chủ yếu để làm cho các tệp C đơn giản của tôi có thể biên dịch được trên msvc và gcc mà không có bất kỳ thay đổi nào, kiểu C89, không #ifdef _MSC_VER, v.v.

Cảm ơn bạn ! Chủ đề này làm cho tôi đăng ký vào trang web này :)


Hãy nhớ rằng không có thứ gọi là "chủ đề" tại trang web này. Stack Overflow có định dạng câu hỏi và câu trả lời, không phải là định dạng chủ đề thảo luận. "Trả lời" không giống như "Trả lời" trong diễn đàn thảo luận; điều đó có nghĩa là bạn thực sự đang cung cấp câu trả lời cho câu hỏi và không nên được sử dụng để trả lời các câu trả lời hoặc nhận xét khác về chủ đề này. Khi bạn có ít nhất 50 đại diện, bạn có thể đăng bình luận , nhưng hãy nhớ đọc phần "Khi nào tôi không nên bình luận?" phần. Vui lòng đọc trang Giới thiệu để hiểu rõ hơn về định dạng của trang web.
Adi Inbar

1

Theo tôi, alloca (), nếu có, chỉ nên được sử dụng một cách hạn chế. Rất giống với việc sử dụng "goto", khá nhiều người hợp lý khác có ác cảm mạnh mẽ không chỉ với việc sử dụng mà còn cả sự tồn tại của alloca ().

Để sử dụng nhúng, trong đó kích thước ngăn xếp được biết đến và có thể áp dụng các giới hạn thông qua quy ước và phân tích về kích thước phân bổ và nơi trình biên dịch không thể được nâng cấp để hỗ trợ C99 +, sử dụng alloca () là tốt, và tôi đã biết sử dụng nó.

Khi khả dụng, VLAs có thể có một số lợi thế so với alloca (): Trình biên dịch có thể tạo các kiểm tra giới hạn ngăn xếp sẽ bắt được quyền truy cập ngoài giới hạn khi truy cập kiểu mảng được sử dụng (Tôi không biết liệu trình biên dịch nào có làm điều này không, nhưng nó có thể được thực hiện) và phân tích mã có thể xác định xem các biểu thức truy cập mảng có được giới hạn đúng hay không. Lưu ý rằng, trong một số môi trường lập trình, như ô tô, thiết bị y tế và hệ thống điện tử, phân tích này phải được thực hiện ngay cả đối với các mảng có kích thước cố định, cả tự động (trên ngăn xếp) và phân bổ tĩnh (toàn cầu hoặc cục bộ).

Trên các kiến ​​trúc lưu trữ cả dữ liệu và trả về địa chỉ / con trỏ khung trên ngăn xếp (từ những gì tôi biết, đó là tất cả chúng), bất kỳ biến phân bổ ngăn xếp nào cũng có thể nguy hiểm vì có thể lấy địa chỉ của biến và các giá trị đầu vào không được kiểm tra có thể cho phép đủ thứ nghịch ngợm.

Tính di động ít được quan tâm trong không gian nhúng, tuy nhiên đó là một lập luận tốt chống lại việc sử dụng alloca () bên ngoài các trường hợp được kiểm soát cẩn thận.

Bên ngoài không gian nhúng, tôi đã sử dụng alloca () chủ yếu bên trong các chức năng ghi nhật ký và định dạng để đạt hiệu quả và trong một máy quét từ vựng không đệ quy, trong đó các cấu trúc tạm thời (được phân bổ sử dụng alloca () được tạo ra trong quá trình token hóa và phân loại, sau đó là liên tục object (được phân bổ thông qua malloc ()) được điền trước khi hàm trả về. Việc sử dụng alloca () cho các cấu trúc tạm thời nhỏ hơn làm giảm đáng kể sự phân mảnh khi đối tượng liên tục được phân bổ.


1

Hầu hết các câu trả lời ở đây phần lớn bỏ lỡ vấn đề: có một lý do tại sao sử dụng _alloca() có khả năng tệ hơn là chỉ lưu trữ các vật thể lớn trong ngăn xếp.

Sự khác biệt chính giữa lưu trữ tự động và _alloca()là cái sau gặp phải một vấn đề (nghiêm trọng) khác: khối được phân bổ không được kiểm soát bởi trình biên dịch , vì vậy không có cách nào để trình biên dịch tối ưu hóa hoặc tái chế nó.

Đối chiếu:

while (condition) {
    char buffer[0x100]; // Chill.
    /* ... */
}

với:

while (condition) {
    char* buffer = _alloca(0x100); // Bad!
    /* ... */
}

Vấn đề với cái sau nên rõ ràng.


Bạn có bất kỳ ví dụ thực tế nào chứng minh sự khác biệt giữa VLA và alloca(vâng, tôi nói là VLA, bởi vì allocakhông chỉ là người tạo ra các mảng có kích thước tĩnh)?
Ruslan

Có những trường hợp sử dụng cho cái thứ hai, cái thứ nhất không hỗ trợ. Tôi có thể muốn có các bản ghi 'n' sau khi vòng lặp được chạy xong 'n' lần - có lẽ trong danh sách hoặc cây được liên kết; cấu trúc dữ liệu này sau đó được xử lý khi cuối cùng hàm trả về. Điều đó không có nghĩa là tôi sẽ mã hóa mọi thứ theo cách đó :-)
greggo

1
Và tôi sẽ nói rằng "trình biên dịch không thể kiểm soát nó" là bởi vì đó là cách mà alloca () được định nghĩa; trình biên dịch hiện đại biết alloca là gì và đặc biệt đối xử với nó; nó không chỉ là một chức năng thư viện như trong những năm 80. C99 VLAs về cơ bản là alloca với phạm vi khối (và gõ tốt hơn). Không kiểm soát nhiều hay ít, chỉ tuân thủ các ngữ nghĩa khác nhau.
greggo

@greggo: Nếu bạn là người downvoter, tôi rất vui khi nghe lý do tại sao bạn nghĩ câu trả lời của tôi không hữu ích.
alecov

Trong C, tái chế không phải là nhiệm vụ của trình biên dịch, thay vào đó là nhiệm vụ của thư viện c (free ()). alloca () được giải phóng khi trở về.
peterh - Phục hồi Monica

1

Tôi không nghĩ rằng bất kỳ ai đã đề cập đến vấn đề này, nhưng alloca cũng có một số vấn đề bảo mật nghiêm trọng không nhất thiết phải có với malloc (mặc dù những vấn đề này cũng phát sinh với bất kỳ mảng dựa trên ngăn xếp nào, động hay không). Vì bộ nhớ được phân bổ trên ngăn xếp, bộ đệm tràn / tràn có hậu quả nghiêm trọng hơn nhiều so với chỉ malloc.

Cụ thể, địa chỉ trả về cho một chức năng được lưu trữ trên ngăn xếp. Nếu giá trị này bị hỏng, mã của bạn có thể được tạo để đi đến bất kỳ vùng bộ nhớ thực thi nào. Trình biên dịch đi đến độ dài lớn để làm cho điều này trở nên khó khăn (đặc biệt là bằng cách ngẫu nhiên bố trí địa chỉ). Tuy nhiên, điều này rõ ràng tồi tệ hơn chỉ là tràn ngăn xếp vì trường hợp tốt nhất là SEGFAULT nếu giá trị trả về bị hỏng, nhưng nó cũng có thể bắt đầu thực thi một phần bộ nhớ ngẫu nhiên hoặc trong trường hợp xấu nhất là một vùng bộ nhớ làm tổn hại đến bảo mật chương trình của bạn .

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.