Hành vi và điểm chuỗi không xác định


987

"Điểm chuỗi" là gì?

Mối quan hệ giữa hành vi không xác định và điểm trình tự là gì?

Tôi thường sử dụng những biểu cảm hài hước và hấp dẫn như thế a[++i] = i;, để khiến bản thân cảm thấy tốt hơn. Tại sao tôi nên ngừng sử dụng chúng?

Nếu bạn đã đọc điều này, hãy chắc chắn truy cập câu hỏi tiếp theo Hành vi không xác định và các điểm trình tự được tải lại .

(Lưu ý: Đây có nghĩa là một mục trong Câu hỏi thường gặp về C ++ của Stack Overflow . Nếu bạn muốn phê bình ý tưởng cung cấp Câu hỏi thường gặp trong biểu mẫu này, thì bài đăng trên meta bắt đầu tất cả điều này sẽ là nơi để thực hiện điều đó. câu hỏi đó được theo dõi trong phòng chat C ++ , nơi ý tưởng FAQ bắt đầu ngay từ đầu, vì vậy câu trả lời của bạn rất có thể được đọc bởi những người nghĩ ra ý tưởng.)

Câu trả lời:


683

C ++ 98 và C ++ 03

Câu trả lời này dành cho các phiên bản cũ hơn của tiêu chuẩn C ++. Các phiên bản C ++ 11 và C ++ 14 của tiêu chuẩn không chính thức chứa 'điểm chuỗi'; thay vào đó, các hoạt động được 'giải trình tự trước' hoặc 'không có kết quả' hoặc 'trình tự không xác định'. Hiệu ứng ròng về cơ bản là giống nhau, nhưng thuật ngữ là khác nhau.


Tuyên bố từ chối trách nhiệm : Được rồi. Câu trả lời này hơi dài. Vì vậy, hãy kiên nhẫn trong khi đọc nó. Nếu bạn đã biết những điều này, đọc lại chúng sẽ không làm bạn phát điên.

Điều kiện tiên quyết : Một kiến ​​thức cơ bản về Tiêu chuẩn C ++


Điểm trình tự là gì?

Tiêu chuẩn nói

Tại một số điểm nhất định trong chuỗi thực hiện được gọi là điểm chuỗi , tất cả các tác dụng phụ của các đánh giá trước sẽ được hoàn thành và không có tác dụng phụ của các đánh giá tiếp theo sẽ xảy ra. (§1.9 / 7)

Phản ứng phụ? Tác dụng phụ là gì?

Đánh giá một biểu thức tạo ra một cái gì đó và nếu ngoài ra có sự thay đổi trạng thái của môi trường thực thi thì người ta nói rằng biểu thức (đánh giá của nó) có một số tác dụng phụ.

Ví dụ:

int x = y++; //where y is also an int

Ngoài hoạt động khởi tạo, giá trị của yđược thay đổi do tác dụng phụ của ++toán tử.

Càng xa càng tốt. Chuyển sang điểm chuỗi. Một định nghĩa xen kẽ các điểm seq được đưa ra bởi tác giả comp.lang.c Steve Summit:

Điểm tuần tự là thời điểm mà bụi đã lắng xuống và tất cả các tác dụng phụ đã được nhìn thấy cho đến nay được đảm bảo hoàn thành.


Các điểm trình tự phổ biến được liệt kê trong Tiêu chuẩn C ++ là gì?

Những người đang có:

  • ở cuối đánh giá biểu thức đầy đủ ( §1.9/16) (Biểu thức đầy đủ là biểu thức không phải là biểu thức con của biểu thức khác.) 1

    Thí dụ :

    int a = 5; // ; is a sequence point here
  • trong việc đánh giá từng biểu thức sau sau khi đánh giá biểu thức đầu tiên ( §1.9/18) 2

    • a && b (§5.14)
    • a || b (§5.15)
    • a ? b : c (§5.16)
    • a , b (§5.18)(ở đây a, b là toán tử dấu phẩy; func(a,a++) ,không phải là toán tử dấu phẩy, nó chỉ là dấu phân cách giữa các đối số aa++. Do đó, hành vi không được xác định trong trường hợp đó (nếu ađược coi là kiểu nguyên thủy))
  • tại một lệnh gọi hàm (có hay không hàm là nội tuyến), sau khi đánh giá tất cả các đối số hàm (nếu có) diễn ra trước khi thực hiện bất kỳ biểu thức hoặc câu lệnh nào trong thân hàm ( §1.9/17).

1: Lưu ý: việc đánh giá biểu thức đầy đủ có thể bao gồm việc đánh giá các biểu thức con không phải là một phần từ vựng của biểu thức đầy đủ. Ví dụ: các biểu thức con liên quan đến việc đánh giá các biểu thức đối số mặc định (8.3.6) được coi là được tạo trong biểu thức gọi hàm, không phải là biểu thức xác định đối số mặc định

2: Các toán tử được chỉ định là các toán tử dựng sẵn, như được mô tả trong điều 5. Khi một trong các toán tử này bị quá tải (mệnh đề 13) trong ngữ cảnh hợp lệ, do đó chỉ định một hàm toán tử do người dùng định nghĩa, biểu thức chỉ định một lệnh gọi hàm và các toán hạng tạo thành một danh sách đối số, không có điểm thứ tự ngụ ý giữa chúng.


Hành vi không xác định là gì?

Tiêu chuẩn định nghĩa Hành vi không xác định trong Phần §1.3.12

hành vi, chẳng hạn như có thể phát sinh khi sử dụng cấu trúc chương trình sai hoặc dữ liệu sai, mà Tiêu chuẩn quốc tế này áp đặt không có yêu cầu 3 .

Hành vi không xác định cũng có thể được dự kiến ​​khi Tiêu chuẩn quốc tế này bỏ qua mô tả về bất kỳ định nghĩa rõ ràng nào về hành vi.

3. (với việc phát hành một thông điệp chẩn đoán).

Nói tóm lại, hành vi không xác định có nghĩa là bất cứ điều gì có thể xảy ra từ daemon bay ra khỏi mũi của bạn để bạn gái của bạn mang thai.


Mối quan hệ giữa Hành vi không xác định và Điểm trình tự là gì?

Trước khi tôi hiểu rằng bạn phải biết (các) sự khác biệt giữa Hành vi không xác định, Hành vi không xác định và Hành vi được xác định thực hiện .

Bạn cũng phải biết rằng the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified.

Ví dụ:

int x = 5, y = 6;

int z = x++ + y++; //it is unspecified whether x++ or y++ will be evaluated first.

Một ví dụ khác ở đây .


Bây giờ Tiêu chuẩn §5/4nói

  • 1) Giữa điểm thứ tự trước và điểm tiếp theo, một đối tượng vô hướng sẽ có giá trị được lưu trữ được sửa đổi nhiều nhất một lần bằng cách đánh giá biểu thức.

Nó có nghĩa là gì?

Một cách không chính thức có nghĩa là giữa hai điểm chuỗi, một biến không được sửa đổi nhiều lần. Trong một tuyên bố biểu thức, next sequence pointthường là ở dấu chấm phẩy kết thúc và previous sequence pointở cuối câu lệnh trước. Một biểu thức cũng có thể chứa trung gian sequence points.

Từ câu trên, các biểu thức sau đây gọi Hành vi không xác định:

i++ * ++i;   // UB, i is modified more than once btw two SPs
i = ++i;     // UB, same as above
++i = 2;     // UB, same as above
i = ++i + 1; // UB, same as above
++++++i;     // UB, parsed as (++(++(++i)))

i = (i, ++i, ++i); // UB, there's no SP between `++i` (right most) and assignment to `i` (`i` is modified more than once btw two SPs)

Nhưng các biểu thức sau đây là tốt:

i = (i, ++i, 1) + 1; // well defined (AFAIK)
i = (++i, i++, i);   // well defined 
int j = i;
j = (++i, i++, j*i); // well defined

  • 2) Hơn nữa, giá trị trước chỉ được truy cập để xác định giá trị sẽ được lưu trữ.

Nó có nghĩa là gì? Nó có nghĩa là nếu một đối tượng được ghi vào trong một biểu thức đầy đủ, bất kỳ và tất cả các truy cập vào nó trong cùng một biểu thức phải được tham gia trực tiếp vào tính toán của giá trị được viết .

Ví dụ, trong i = i + 1tất cả các truy cập của i(trong LHS và trong RHS) đều liên quan trực tiếp đến việc tính toán giá trị được viết. Vậy là ổn rồi.

Quy tắc này hạn chế một cách hiệu quả các biểu thức pháp lý đối với những người truy cập trước khi sửa đổi.

Ví dụ 1:

std::printf("%d %d", i,++i); // invokes Undefined Behaviour because of Rule no 2

Ví dụ 2:

a[i] = i++ // or a[++i] = i or a[i++] = ++i etc

không được phép vì một trong những quyền truy cập của i(một trong a[i]) không liên quan gì đến giá trị cuối cùng được lưu trữ trong i (xảy ra trong i++) và vì vậy không có cách nào tốt để xác định - theo cách hiểu của chúng tôi hoặc trình biên dịch - cho dù việc truy cập nên diễn ra trước hay sau khi giá trị gia tăng được lưu trữ. Vì vậy, hành vi là không xác định.

Ví dụ 3:

int x = i + i++ ;// Similar to above

Theo dõi câu trả lời cho C ++ 11 tại đây .


45
*p++ = 4 không xác định hành vi. *p++được hiểu là *(p++). p++trả về p(một bản sao) và giá trị được lưu trữ tại địa chỉ trước đó. Tại sao điều đó sẽ gọi UB? Nó là hoàn toàn tốt.
Prasoon Saurav

7
@Mike: AFAIK, không có bản sao (hợp pháp) nào của Tiêu chuẩn C ++ mà bạn có thể liên kết đến.
sbi

11
Chà, sau đó bạn có thể có một liên kết đến trang đặt hàng có liên quan của ISO. Dù sao, nghĩ về nó, cụm từ "kiến thức cơ bản về tiêu chuẩn C ++" có vẻ hơi mâu thuẫn về mặt thuật ngữ, vì nếu bạn đang đọc tiêu chuẩn, bạn đã vượt qua cấp tiểu học. Có lẽ chúng ta có thể liệt kê những thứ trong ngôn ngữ bạn cần hiểu cơ bản, như cú pháp biểu thức, thứ tự các thao tác và có thể quá tải toán tử?
Mike DeSimone

41
Tôi không chắc trích dẫn tiêu chuẩn là cách tốt nhất để dạy người mới
Nghịch đảo

6
@Adrian Biểu thức đầu tiên gọi một UB vì không có điểm thứ tự giữa phần cuối ++ivà phần gán cho i. Biểu thức thứ hai không gọi UB vì biểu thức ikhông thay đổi giá trị của i. Trong ví dụ thứ hai, i++nó được theo sau bởi một điểm chuỗi ( ,) trước khi toán tử gán được gọi.
Kolyunya

276

Đây là phần tiếp theo câu trả lời trước của tôi và chứa tài liệu liên quan đến C ++ 11. .


Điều kiện tiên quyết : Một kiến ​​thức cơ bản về Quan hệ (Toán học).


Có đúng là không có Điểm thứ tự trong C ++ 11 không?

Đúng! Thật là quá đúng.

Các điểm trình tự đã được thay thế bằng các mối quan hệ tuần tự trướctuần tự sau (và không liên tụckhông xác định trình tự ) trong C ++ 11.


Chính xác thì đây là thứ gì 'Sắp xếp trước'?

Trình tự trước (§1.9 / 13) là một mối quan hệ:

giữa các đánh giá được thực hiện bởi một luồng duy nhất và tạo ra một thứ tự nghiêm ngặt 1

Chính thức nó có nghĩa là đưa ra bất kỳ hai đánh giá (Xem bên dưới) AB, nếu Ađược giải trình tự trước B , thì việc thực hiện A sẽ đi trước việc thực hiện B. Nếu Akhông được giải trình tự trước BBkhông được giải trình tự trước A, thì ABkhông có kết quả 2 .

Đánh giá ABđược giải trình tự không xác định khi một trong hai Ađược sắp xếp trước Bhoặc Bđược giải trình tự trước A, nhưng không xác định được 3 .

[LƯU Ý]
1: Một trật tự một phần nghiêm ngặt là một mối quan hệ nhị phân "<" trên một tập Pđó là asymmetric, và transitive, tức là, cho tất cả a, bctrong P, ta có:
........ (i). nếu a <b thì (b <a) ( asymmetry);
........ (ii). nếu a <b và b <c thì a <c ( transitivity).
2: Việc thực hiện các đánh giá không có kết quả có thể chồng chéo .
3: Các đánh giá theo trình tự không xác định có thể trùng lặp , nhưng có thể được thực hiện trước.


Ý nghĩa của từ 'đánh giá' trong ngữ cảnh của C ++ 11 là gì?

Trong C ++ 11, đánh giá biểu thức (hoặc biểu thức con) nói chung bao gồm:

  • tính toán giá trị (bao gồm xác định danh tính của một đối tượng để đánh giá glvalue và tìm nạp một giá trị được gán trước đó cho một đối tượng để đánh giá prvalue ) và

  • bắt đầu tác dụng phụ .

Bây giờ (§1.9 / 14) nói:

Mọi tính toán giá trị và hiệu ứng phụ liên quan đến biểu thức đầy đủ được sắp xếp theo thứ tự trước mỗi tính toán giá trị và tác dụng phụ liên quan đến biểu thức đầy đủ tiếp theo được đánh giá .

  • Ví dụ tầm thường:

    int x; x = 10; ++x;

    Tính toán giá trị và tác dụng phụ liên quan đến ++xđược giải trình tự sau khi tính toán giá trị và tác dụng phụ củax = 10;


Vì vậy, phải có một số mối quan hệ giữa Hành vi không xác định và những điều nêu trên, phải không?

Đúng! Đúng.

Trong (§1.9 / 15), nó đã được đề cập rằng

Trừ khi có ghi chú, các đánh giá toán hạng của các toán tử riêng lẻ và các biểu thức con của các biểu thức riêng lẻ không được đưa ra 4 .

Ví dụ :

int main()
{
     int num = 19 ;
     num = (num << 3) + (num >> 3);
} 
  1. Đánh giá toán hạng của +toán tử là không có kết quả tương đối với nhau.
  2. Đánh giá toán hạng của <<>>toán tử là không có kết quả tương đối với nhau.

4: Trong một biểu thức được đánh giá nhiều hơn một lần trong thời gian thực hiện một chương trình, unsequencedindeterminately trình tự đánh giá của subexpressions của nó không cần phải được thực hiện một cách nhất quán trong đánh giá khác nhau.

(§1.9 / 15) Các tính toán giá trị của các toán hạng của toán tử được sắp xếp theo trình tự trước khi tính toán giá trị của kết quả của toán tử.

Điều đó có nghĩa là trong x + ytính toán giá trị của xyđược giải trình tự trước khi tính toán giá trị của (x + y).

Quan trọng hơn

(§1.9 / 15) Nếu tác dụng phụ lên một đối tượng vô hướng không có kết quả tương ứng với một trong hai

(a) một tác dụng phụ khác trên cùng một đối tượng vô hướng

hoặc là

(b) một tính toán giá trị sử dụng giá trị của cùng một đối tượng vô hướng.

hành vi không xác định .

Ví dụ:

int i = 5, v[10] = { };
void  f(int,  int);
  1. i = i++ * ++i; // Undefined Behaviour
  2. i = ++i + i++; // Undefined Behaviour
  3. i = ++i + ++i; // Undefined Behaviour
  4. i = v[i++]; // Undefined Behaviour
  5. i = v[++i]: // Well-defined Behavior
  6. i = i++ + 1; // Undefined Behaviour
  7. i = ++i + 1; // Well-defined Behaviour
  8. ++++i; // Well-defined Behaviour
  9. f(i = -1, i = -1); // Undefined Behaviour (see below)

Khi gọi một hàm (có hay không là hàm nội tuyến), mọi tính toán giá trị và hiệu ứng phụ liên quan đến bất kỳ biểu thức đối số nào hoặc với biểu thức postfix chỉ định hàm được gọi, được sắp xếp theo thứ tự trước khi thực hiện mọi biểu thức hoặc câu lệnh trong phần thân của gọi là hàm. [ Lưu ý: Tính toán giá trị và tác dụng phụ liên quan đến các biểu thức đối số khác nhau không được xử lý . - lưu ý cuối ]

Biểu hiện (5), (7)(8)không gọi hành vi không xác định. Kiểm tra các câu trả lời sau đây để được giải thích chi tiết hơn.


Lưu ý cuối cùng :

Nếu bạn tìm thấy bất kỳ sai sót trong bài viết xin vui lòng để lại nhận xét. Người dùng quyền lực (Với đại diện> 20000) xin vui lòng chỉnh sửa bài đăng để sửa lỗi chính tả và các lỗi khác.


3
Thay vì "không đối xứng", trình tự trước / sau là quan hệ "không đối xứng". Điều này nên được thay đổi trong văn bản để phù hợp với định nghĩa của một thứ tự từng phần được đưa ra sau này (cũng đồng ý với Wikipedia).
TemplateRex

1
Tại sao 7) mục trong ví dụ cuối cùng là một UB? Có lẽ nó nên f(i = -1, i = 1)?
Mikhail

1
Tôi đã sửa mô tả về mối quan hệ "tuần tự trước". Đó là một thứ tự nghiêm ngặt một phần . Rõ ràng, một biểu thức không thể được giải trình tự trước chính nó, vì vậy mối quan hệ không thể là phản xạ. Do đó nó không đối xứng không đối xứng.
ThomasMcLeod

1
5) được hoàn thiện tốt thổi tâm trí của tôi đi. lời giải thích của Johannes Schaub không hoàn toàn đơn giản để có được. Đặc biệt bởi vì tôi tin rằng ngay cả trong ++i(được đánh giá giá trị trước +toán tử đang sử dụng nó), tiêu chuẩn vẫn không nói rằng tác dụng phụ của nó phải được hoàn thành. Nhưng trên thực tế, bởi vì nó trả về một ref để một lvaluemà là ichính nó, nó PHẢI đã hoàn thành tác dụng phụ kể từ khi đánh giá phải được hoàn thành, do đó giá trị phải được cập nhật. Đây là phần điên rồ để có được trong thực tế.
v.oddou

"Các thành viên Ủy ban ISO C ++ nghĩ rằng các công cụ Sequence Points khá khó hiểu. Vì vậy, họ quyết định thay thế nó bằng các mối quan hệ đã đề cập ở trên chỉ để diễn đạt rõ ràng hơn và tăng cường sự chính xác." - bạn đã có một tài liệu tham khảo cho yêu cầu đó? Dường như với tôi rằng các mối quan hệ mới là khó hiểu hơn.
MM

30

C ++ 17 ( N4659) bao gồm một đề xuất Thứ tự đánh giá biểu thức tinh chỉnh cho thành ngữ C ++ , định nghĩa một thứ tự chặt chẽ hơn của đánh giá biểu thức.

Cụ thể, câu sau

8.18 Toán tử chuyển nhượng và gán ghép :
....

Trong mọi trường hợp, phép gán được sắp xếp theo trình tự sau khi tính toán giá trị của toán hạng phải và trái và trước khi tính toán giá trị của biểu thức gán. Toán hạng bên phải được sắp xếp theo thứ tự trước toán hạng bên trái.

cùng với sự làm rõ sau đây

Một biểu thức X được cho là được lập trình tự trước một biểu thức Y nếu mọi tính toán giá trị và mỗi tác dụng phụ liên quan đến việc biểu hiện X được lập trình tự trước mỗi tính toán giá trị và mỗi tác dụng phụ liên quan đến việc biểu Y .

làm cho một số trường hợp hành vi không xác định trước đó hợp lệ, bao gồm một trong những câu hỏi:

a[++i] = i;

Tuy nhiên một số trường hợp tương tự khác vẫn dẫn đến hành vi không xác định.

Trong N4140:

i = i++ + 1; // the behavior is undefined

Nhưng trong N4659

i = i++ + 1; // the value of i is incremented
i = i++ + i; // the behavior is undefined

Tất nhiên, sử dụng trình biên dịch tuân thủ C ++ 17 không nhất thiết có nghĩa là người ta nên bắt đầu viết các biểu thức như vậy.


Tại sao i = i++ + 1;được xác định hành vi trong c ++ 17, tôi nghĩ ngay cả khi "Toán hạng bên phải được sắp xếp theo thứ tự trước toán hạng bên trái", tuy nhiên việc sửa đổi cho "i ++" và hiệu ứng phụ cho việc gán không được giải quyết, vui lòng cung cấp thêm chi tiết để diễn giải những điều này
jack X

@jackX Mình mở rộng câu trả lời :).
AlexD

yup, tôi nghĩ rằng chi tiết diễn giải câu "Toán hạng bên phải được sắp xếp theo thứ tự trước toán hạng bên trái" hữu ích hơn vì "Toán hạng bên phải được giải trình tự trước toán hạng bên trái" có nghĩa là tính toán giá trị và hiệu ứng phụ liên quan đến toán hạng bên phải giải trình tự trước đó của toán hạng trái. như bạn đã làm :-)
jack X

11

Tôi đoán có một lý do cơ bản cho sự thay đổi, nó không chỉ đơn thuần là mỹ phẩm để làm cho diễn giải cũ rõ ràng hơn: lý do đó là đồng thời. Thứ tự xây dựng không xác định chỉ đơn thuần là lựa chọn một trong một số thứ tự nối tiếp có thể, điều này hoàn toàn khác với thứ tự trước và sau khi đặt hàng, bởi vì nếu không có thứ tự cụ thể, có thể đánh giá đồng thời: không như vậy với các quy tắc cũ. Ví dụ trong:

f (a,b)

trước đây a sau đó b, hoặc, b sau đó a. Bây giờ, a và b có thể được đánh giá bằng các hướng dẫn xen kẽ hoặc thậm chí trên các lõi khác nhau.


5
Tuy nhiên, tôi tin rằng nếu 'a' hoặc 'b' bao gồm một lệnh gọi hàm, chúng sẽ được giải trình tự không rõ ràng thay vì không được xử lý, nghĩa là tất cả các tác dụng phụ từ một được yêu cầu xảy ra trước bất kỳ tác dụng phụ nào từ khác, mặc dù trình biên dịch không nhất quán về cái nào đi trước. Nếu điều đó không còn đúng nữa, nó sẽ phá vỡ rất nhiều mã dựa trên các hoạt động không chồng chéo (ví dụ: nếu 'a' và 'b' mỗi thiết lập, sử dụng và gỡ xuống, trạng thái tĩnh được chia sẻ).
supercat

2

Trong C99(ISO/IEC 9899:TC3)đó dường như vắng mặt trong cuộc thảo luận này cho đến nay các stete sau đây được thực hiện liên quan đến thứ tự của evaluaiton.

[...] Thứ tự đánh giá các biểu hiện phụ và thứ tự xảy ra các tác dụng phụ đều không xác định. (Mục 6.5 trang 67)

Thứ tự đánh giá các toán hạng là không xác định. Nếu một nỗ lực được thực hiện để sửa đổi kết quả của toán tử gán hoặc để truy cập nó sau điểm chuỗi tiếp theo, hành vi [sic] không được xác định. (Mục 6.5.16 trang 91)


2
Câu hỏi được gắn thẻ C ++ chứ không phải C, điều này tốt vì hành vi trong C ++ 17 khá khác so với hành vi trong các phiên bản cũ hơn - và không liên quan đến hành vi trong C11, C99, C90, v.v. Hoặc chịu rất ít liên quan đến nó. Nhìn chung, tôi đề nghị loại bỏ điều này. Quan trọng hơn, chúng ta cần tìm Q & A tương đương cho C và đảm bảo rằng nó ổn (và đặc biệt lưu ý rằng C ++ 17, đặc biệt, thay đổi các quy tắc - hành vi trong C ++ 11 và trước đó ít nhiều giống như trong C11, mặc dù phiên bản mô tả bằng C vẫn sử dụng 'điểm chuỗi' trong khi C ++ 11 và sau đó thì không.
Jonathan Leffler
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.