Khi tôi `ném` thứ gì đó, nó sẽ được lưu trữ ở đâu trong bộ nhớ?


82

Tôi hiểu rằng khi thứ gì đó là thrown, ngăn xếp 'không liên kết' đến điểm nó bị bắt và các hàm hủy của các cá thể lớp trên ngăn xếp trong mỗi ngữ cảnh hàm được chạy (đó là lý do tại sao bạn không nên ném một ngoại lệ từ trình hủy - bạn có thể ném một cái thứ hai) ... nhưng tôi tự hỏi đối tượng mà tôi đã ném được lưu trữ ở đâu trong bộ nhớ khi điều này xảy ra?

Việc thực hiện có phụ thuộc không? Nếu vậy, có phương pháp cụ thể nào được hầu hết các trình biên dịch phổ biến sử dụng không?


1
Câu hỏi hay - nó có thể không được quy định bởi tiêu chuẩn, vì tiêu chuẩn thậm chí không nói rằng bạn phải có một ngăn xếp. Thực tế, có lẽ nó được phân bổ trên heap và được giải phóng khi khối bắt thoát ra?
Kerrek SB

@Kerrek: Tôi nghĩ nhiều khả năng, đối tượng được đặt trên ngăn xếp theo cách xuống "dưới cùng". Sau đó, hãy tua ngược lên trên, ghi nhớ vị trí của nó và khi bạn đã hoàn thành việc tua (bao gồm cả việc tạo cơ hội cho bất kỳ mệnh đề nắm bắt nào để giải quyết ngoại lệ bằng cách tham chiếu và lặp lại raise), hãy hủy nó. Vấn đề với phân bổ heap là, bạn sẽ làm gì nếu nỗ lực phân bổ ngoại lệ không thành công? Bạn sẽ phải hủy bỏ (giống như cách bạn làm đối với trường hợp tràn ngăn xếp nếu đặt nó vào ngăn xếp "không thành công" do thiếu dung lượng). Không giống như Java, việc triển khai không được phép tạo ra một ngoại lệ khác.
Steve Jessop

@Steve: Cũng có thể - Bài báo của Kiril nói rằng ngoại lệ được cấp phát trên ngăn xếp và một cấu trúc thông tin bổ trợ ghi lại địa chỉ và trình phân tách của nó, v.v., nhưng tôi tưởng tượng rằng các triển khai có thể thực hiện điều này theo bất kỳ cách nào họ muốn?
Kerrek SB

@Kerrek: vâng, tùy thuộc vào ràng buộc rằng nó phải thực sự ném ngoại lệ và vấn đề thông thường là hết ngăn xếp cho phép việc triển khai trốn tránh các trách nhiệm như vậy :-) Nếu bạn đặt nó vào ngăn xếp, thì vì ngoại lệ được ném bởi kiểu tĩnh của biểu thức ném, khung ngăn xếp của bạn cho toàn bộ hàm có thể bao gồm bất kỳ khoảng trống nào cần thiết để ném kiểu đó, mặc dù tôi không biết liệu MSVC có làm điều đó hay không.
Steve Jessop

Câu trả lời:


51

Có, câu trả lời là phụ thuộc vào trình biên dịch.

Một thử nghiệm nhanh với trình biên dịch của tôi ( g++ 4.4.3) cho thấy rằng thư viện thời gian chạy của nó trước tiên cố gắng malloclưu trữ ngoại lệ và thất bại đó, cố gắng phân bổ không gian trong một "bộ đệm khẩn cấp" toàn quy trình nằm trên phân đoạn dữ liệu. Nếu điều đó không thành công, nó sẽ gọi std::terminate().

Có vẻ như mục đích chính của bộ đệm khẩn cấp là có thể ném std::bad_allocsau khi quá trình chạy hết dung lượng heap (trong trường hợp đó malloccuộc gọi sẽ thất bại).

Chức năng liên quan là __cxa_allocate_exception:

extern "C" void *
__cxxabiv1::__cxa_allocate_exception(std::size_t thrown_size) throw()
{
  void *ret;

  thrown_size += sizeof (__cxa_refcounted_exception);
  ret = malloc (thrown_size);

  if (! ret)
    {
      __gnu_cxx::__scoped_lock sentry(emergency_mutex);

      bitmask_type used = emergency_used;
      unsigned int which = 0;

      if (thrown_size > EMERGENCY_OBJ_SIZE)
        goto failed;
      while (used & 1)
        {
          used >>= 1;
          if (++which >= EMERGENCY_OBJ_COUNT)
            goto failed;
        }

      emergency_used |= (bitmask_type)1 << which;
      ret = &emergency_buffer[which][0];

    failed:;

      if (!ret)
        std::terminate ();
    }

  // We have an uncaught exception as soon as we allocate memory.  This
  // yields uncaught_exception() true during the copy-constructor that
  // initializes the exception object.  See Issue 475.
  __cxa_eh_globals *globals = __cxa_get_globals ();
  globals->uncaughtExceptions += 1;

  memset (ret, 0, sizeof (__cxa_refcounted_exception));

  return (void *)((char *)ret + sizeof (__cxa_refcounted_exception));
}

Tôi không biết chương trình này điển hình như thế nào.


"trong trường hợp đó malloccuộc gọi sẽ thất bại", thông thường, nhưng không nhất thiết.
Ayxan Haqverdili

20

Từ trang này :

Lưu trữ là cần thiết cho các trường hợp ngoại lệ được ném ra. Bộ nhớ này phải tồn tại trong khi ngăn xếp đang được mở, vì nó sẽ được trình xử lý sử dụng và phải an toàn theo luồng. Do đó, việc lưu trữ đối tượng ngoại lệ sẽ thường được cấp phát trong heap , mặc dù việc triển khai có thể cung cấp một bộ đệm khẩn cấp để hỗ trợ ném các ngoại lệ bad_alloc trong điều kiện bộ nhớ thấp.

Bây giờ, đây chỉ là Itanium ABI và tôi đang tìm kiếm các chi tiết cụ thể cho GCC, Clang và MSVC. Tuy nhiên, tiêu chuẩn không chỉ định bất kỳ điều gì và đây dường như là cách rõ ràng để triển khai lưu trữ ngoại lệ, vì vậy ...


1
Bạn vẫn đang tìm kiếm thông tin chi tiết? Đôi khi nó muốn được tốt đẹp để có thể chấp nhận nhiều hơn một câu trả lời :)
sje397

4

Tôi không biết liệu điều này có trả lời câu hỏi của bạn hay không, nhưng đây (Cách trình biên dịch C ++ thực hiện xử lý ngoại lệ) là một bài viết tuyệt vời về cách xử lý ngoại lệ:. Tôi khuyên bạn nên nó (:

Xin lỗi vì câu trả lời ngắn, nhưng toàn bộ thông tin trong bài viết rất hay, tôi không thể chọn và đăng một số thông tin ở đây.


Tìm kiếm excpt_infotrong trang đó, nó cung cấp một số thông tin về cách MSVC thực hiện.
Steve Jessop

1
Liên kết đó thực sự mô tả cách không làm điều đó trong một triển khai tốt. Tôi biết một số VC ++ cũ hơn đã sử dụng một cái gì đó như thế này, nhưng tôi nghi ngờ bạn sẽ tìm thấy nó trong bất kỳ trình biên dịch hiện đại nào.
James Kanze

0

Tiêu chuẩn C ++ thường chỉ định cách ngôn ngữ hoạt động nhưng không chỉ định cách trình biên dịch thực hiện hành vi đó. Tôi nghĩ câu hỏi này thuộc loại đó. Cách tốt nhất để thực hiện một cái gì đó như thế này phụ thuộc vào các chi tiết cụ thể của máy - một số bộ xử lý có rất nhiều thanh ghi mục đích chung, một số có rất ít. Một bộ xử lý thậm chí có thể được xây dựng với một thanh ghi đặc biệt chỉ dành cho các trường hợp ngoại lệ, trong trường hợp đó, trình biên dịch sẽ được tự do tận dụng tính năng đó.


-2

Chà, nó không thể nằm trong ngăn xếp, vì nó sẽ không được gắn và nó không thể nằm trên đống, vì điều đó có nghĩa là hệ thống có khả năng không thể ném std::bad_alloc. Ngoài ra, nó hoàn toàn phụ thuộc vào việc triển khai: không chỉ định triển khai (phải được lập thành văn bản), nhưng không xác định. (Một triển khai có thể sử dụng heap hầu hết thời gian, miễn là nó có một số loại sao lưu khẩn cấp sẽ cho phép một số ngoại lệ giới hạn ngay cả khi không còn bộ nhớ.)


3
Câu trả lời của bạn mâu thuẫn với chính nó.
Lightness Races ở Orbit

Làm sao? Nó trình bày các ràng buộc đối với việc triển khai, được ngầm định trong tiêu chuẩn.
James Kanze

Một ví dụ: "nó không thể nằm trên heap" ... "Việc triển khai có thể sử dụng heap hầu hết thời gian". Ngoài ra, toàn bộ nửa đầu của câu trả lời là sai, như đã giải thích trong các câu trả lời khác (và thực sự là một phần trong phần thứ hai của bạn!).
Các cuộc đua ánh sáng trong quỹ đạo

Tiêu chuẩn yêu cầu std::bad_allocđể hoạt động. Điều này có nghĩa là một triển khai không thể sử dụng heap một cách có hệ thống. Tiêu chuẩn không cho phép nó. Tôi đang dựa trên tuyên bố của mình về tiêu chuẩn.
James Kanze

FSVO của "một cách có hệ thống", điều đó chính xác. Tuy nhiên, như bạn đã nêu trong phần thứ hai của câu trả lời, việc triển khai thực sự có thể sử dụng heap ^ H ^ H ^ H ^ Hfreestore kết hợp với các lựa chọn thay thế.
Lightness Races ở Orbit
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.