Có sử dụng các biến rò rỉ goto không?


94

Có đúng là gotonhảy qua các bit mã mà không gọi hàm hủy và mọi thứ không?

ví dụ

void f() {
   int x = 0;
   goto lol;
}

int main() {
   f();
lol:
   return 0;
}

Sẽ không xbị rò rỉ?


Liên quan: stackoverflow.com/questions/1258201/... (nhưng tôi muốn làm điều đó từ đầu, sạch!)
Lightness Races ở Orbit

15
Nghĩa "Won't x be leaked"là gì? Kiểu của xlà một kiểu dữ liệu tích hợp sẵn. Tại sao bạn không chọn một ví dụ tốt hơn?
Nawaz

2
@Nawaz: Ví dụ hoàn hảo theo cách của nó. Hầu như mỗi khi tôi nói chuyện với ai đó goto, họ nghĩ rằng ngay cả các biến thời lượng lưu trữ tự động cũng bị "rò rỉ" bằng cách nào đó. Điều đó bạn và tôi biết khác là hoàn toàn bên cạnh vấn đề.
Các cuộc đua ánh sáng trong quỹ đạo

1
@David: Tôi đồng ý rằng câu hỏi này có ý nghĩa hơn rất nhiều khi biến có hàm hủy không tầm thường ... và tôi đã xem câu trả lời của Tomalak và đã tìm thấy một ví dụ như vậy. Hơn nữa, trong khi intkhông thể rò rỉ, nó có thể bị rò rỉ . Ví dụ: void f(void) { new int(5); }rò rỉ một int.
Ben Voigt

Tại sao không thay đổi câu hỏi thành một cái gì đó như "Trong ví dụ đã cho, liệu đường dẫn thực thi mã có chuyển từ f () sang main () mà không xóa ngăn xếp và chức năng trả về từ hàm khác không? Sẽ có vấn đề nếu một trình hủy được gọi là? Có giống nhau trong C không? " Liệu điều đó có duy trì được ý định của câu hỏi, đồng thời tránh được những quan niệm sai lầm có thể xảy ra không?
Jack V.

Câu trả lời:


210

Cảnh báo: Câu trả lời này gắn liền với C ++ chỉ ; các quy tắc khá khác nhau trong C.


Sẽ không xbị rò rỉ?

Không, hoàn toàn không.

Thật hoang đường khi gotomột số cấu trúc cấp thấp cho phép bạn ghi đè các cơ chế xác định phạm vi tích hợp của C ++. (Nếu có bất cứ điều gì, nó longjmpcó thể dễ bị điều này.)

Hãy xem xét các cơ chế sau đây ngăn bạn làm "điều xấu" với nhãn (bao gồm cả casenhãn).


1. Phạm vi nhãn

Bạn không thể chuyển qua các chức năng:

void f() {
   int x = 0;
   goto lol;
}

int main() {
   f();
lol:
   return 0;
}

// error: label 'lol' used but not defined

[n3290: 6.1/1]:[..] Phạm vi của nhãn là chức năng mà nó xuất hiện. [..]


2. Khởi tạo đối tượng

Bạn không thể chuyển qua khởi tạo đối tượng:

int main() {
   goto lol;
   int x = 0;
lol:
   return 0;
}

// error: jump to label ‘lol’
// error:   from here
// error:   crosses initialization of ‘int x’

Nếu bạn quay lại quá trình khởi tạo đối tượng, thì "phiên bản" trước đó của đối tượng sẽ bị phá hủy :

struct T {
   T() { cout << "*T"; }
  ~T() { cout << "~T"; }
};

int main() {
   int x = 0;

  lol:
   T t;
   if (x++ < 5)
     goto lol;
}

// Output: *T~T*T~T*T~T*T~T*T~T*T~T

[n3290: 6.6/2]:[..] Chuyển ra khỏi vòng lặp, ra khỏi khối hoặc quay lại một biến đã khởi tạo với thời lượng lưu trữ tự động liên quan đến việc phá hủy các đối tượng có thời hạn lưu trữ tự động nằm trong phạm vi tại điểm được chuyển từ nhưng không phải tại điểm được chuyển đến . [..]

Bạn không thể nhảy vào phạm vi của một đối tượng, ngay cả khi nó không được khởi tạo rõ ràng:

int main() {
   goto lol;
   {
      std::string x;
lol:
      x = "";
   }
}

// error: jump to label ‘lol’
// error:   from here
// error:   crosses initialization of ‘std::string x’

... ngoại trừ một số loại đối tượng mà ngôn ngữ có thể xử lý bất kể vì chúng không yêu cầu cấu trúc "phức tạp":

int main() {
   goto lol;
   {
      int x;
lol:
      x = 0;
   }
}

// OK

[n3290: 6.7/3]:Có thể chuyển vào một khối, nhưng không phải theo cách bỏ qua khai báo với khởi tạo. Một chương trình nhảy từ một điểm mà một biến có thời lượng lưu trữ tự động không nằm trong phạm vi đến một điểm mà nó trong phạm vi không được định hình trừ khi biến có kiểu vô hướng, kiểu lớp với một hàm tạo mặc định nhỏ và một hàm hủy tầm thường, a phiên bản đủ điều kiện cv của một trong các kiểu này hoặc một mảng của một trong các kiểu trước đó và được khai báo mà không có trình khởi tạo. [..]


3. Nhảy phụ thuộc vào phạm vi của các đối tượng khác

Tương tự như vậy, các đối tượng với thời gian lưu trữ tự động không "rò rỉ" khi bạn gotora khỏi phạm vi của họ :

struct T {
   T() { cout << "*T"; }
  ~T() { cout << "~T"; }
};

int main() {
   {
      T t;
      goto lol;
   }

lol:
   return 0;
}

// *T~T

[n3290: 6.6/2]:Khi thoát khỏi một phạm vi (tuy nhiên đã hoàn thành), các đối tượng có thời gian lưu trữ tự động (3.7.3) đã được xây dựng trong phạm vi đó sẽ bị phá hủy theo thứ tự ngược lại với quá trình xây dựng của chúng. [..]


Phần kết luận

Các cơ chế trên đảm bảo rằng gotokhông cho phép bạn phá vỡ ngôn ngữ.

Tất nhiên, điều này không tự động có nghĩa là bạn "nên" sử dụng gotocho bất kỳ vấn đề nhất định nào, nhưng nó nghĩa là nó không gần như "xấu xa" như lầm tưởng phổ biến khiến mọi người tin tưởng.


8
Bạn có thể lưu ý rằng C không ngăn chặn tất cả những điều nguy hiểm này xảy ra.
Daniel

13
@Daniel: Câu hỏi và câu trả lời rất cụ thể về C ++, nhưng điểm công bằng. Có lẽ chúng ta có thể có một câu hỏi thường gặp xua tan huyền thoại rằng C và C ++ đều giống nhau;)
Lightness Races ở Orbit

3
@Tomalak: Tôi không nghĩ rằng chúng ta đang bất đồng ở đây. Nhiều câu trả lời được đưa ra trên SO được ghi lại rõ ràng ở đâu đó. Tôi chỉ làm cho thời điểm đó nó có thể được hấp dẫn cho một lập trình viên C để xem câu trả lời này và cho rằng nếu nó hoạt động trong C ++, nó cũng làm việc tương tự như trong C.
Daniel

2
Bạn cũng có thể muốn thêm rằng tất cả những thứ nhảy qua intialization này đều giống nhau đối với các nhãn chữ hoa.
PlasmaHH

12
Chà, tôi vừa cho rằng ngữ nghĩa của C ++ đã bị hỏng đối với goto, nhưng họ rất lành mạnh! Câu trả lời chính xác.
Joseph Garvin
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.