พฤติกรรมที่ไม่ได้กำหนดทั่วไปทั้งหมดที่โปรแกรมเมอร์ C ++ ควรรู้คืออะไร [ปิด]


201

พฤติกรรมที่ไม่ได้กำหนดทั่วไปทั้งหมดที่โปรแกรมเมอร์ C ++ ควรรู้คืออะไร

พูดเช่น:

a[i] = i++;


3
คุณแน่ใจไหม. ที่ดูชัดเจน
Martin York

17
6.2.2 ลำดับการประเมินผล [expr.evaluation] ในภาษาการเขียนโปรแกรม C ++ พูดอย่างนั้นฉันไม่ได้มีการอ้างอิงอื่น ๆ
yesraaj

4
เขาพูดถูก .. ดูที่ 6.2.2 ในภาษาการเขียนโปรแกรม C ++ และมันบอกว่า v [i] = i ++ ไม่ได้ถูกกำหนด
dancavallaro

4
ฉันจะจินตนาการเพราะคอมไพเลอร์ทำการรัน i ++ ก่อนหรือหลังการคำนวณตำแหน่งหน่วยความจำของ v [i] แน่นอนฉันจะได้รับมอบหมายเสมอ แต่มันอาจจะเขียนถึงทั้งวี [I] หรือ v [i + 1] ขึ้นอยู่กับคำสั่งของการดำเนินงาน ..
Evan Teran

2
สิ่งที่ภาษาโปรแกรม C ++ บอกคือ "ลำดับของการดำเนินการของนิพจน์ย่อยภายในนิพจน์นั้นไม่ได้กำหนดไว้โดยเฉพาะคุณไม่สามารถสันนิษฐานได้ว่านิพจน์นั้นได้รับการประเมินจากซ้ายไปขวา"
dancavallaro

คำตอบ:


233

ชี้

  • การอ้างอิงNULLตัวชี้
  • การอ้างอิงตัวชี้กลับโดยการจัดสรร "ใหม่" ของขนาดศูนย์
  • การใช้ตัวชี้ไปยังวัตถุที่อายุการใช้งานสิ้นสุดลง (ตัวอย่างเช่นการจัดสรรวัตถุแบบกองซ้อนหรือวัตถุที่ถูกลบ)
  • การลงทะเบียนตัวชี้ที่ยังไม่ได้เริ่มต้นแน่นอน
  • การดำเนินการทางคณิตศาสตร์พอยน์เตอร์ที่ให้ผลลัพธ์นอกขอบเขต (ทั้งด้านบนหรือด้านล่าง) ของอาร์เรย์
  • การยกเลิกการอ้างอิงตัวชี้ที่ตำแหน่งนอกเหนือจากจุดสิ้นสุดของอาร์เรย์
  • การแปลงพอยน์เตอร์เป็นออบเจ็กต์ประเภทที่เข้ากันไม่ได้
  • ใช้memcpyเพื่อคัดลอกบัฟเฟอร์ที่ทับซ้อนกัน

บัฟเฟอร์ล้น

  • การอ่านหรือการเขียนไปยังวัตถุหรืออาร์เรย์ที่ออฟเซ็ตที่เป็นค่าลบหรือเกินขนาดของวัตถุนั้น (ล้น / กองมากเกินไป)

ล้นจำนวนเต็ม

  • ล้นจำนวนเต็มลงนาม
  • การประเมินการแสดงออกที่ไม่ได้กำหนดทางคณิตศาสตร์
  • ค่าการเลื่อนซ้ายตามจำนวนติดลบ (การเลื่อนขวาด้วยจำนวนติดลบถูกกำหนดไว้ในการติดตั้ง)
  • เลื่อนค่าโดยจำนวนที่มากกว่าหรือเท่ากับจำนวนบิตในจำนวน (เช่นint64_t i = 1; i <<= 72ไม่ได้กำหนด)

ประเภทนักแสดงและ Const

  • การส่งค่าตัวเลขไปเป็นค่าที่ไม่สามารถแสดงด้วยประเภทเป้าหมาย (โดยตรงหรือผ่าน static_cast)
  • การใช้ตัวแปรอัตโนมัติก่อนที่มันจะถูกกำหนดอย่างแน่นอน (เช่นint i; i++; cout << i;)
  • การใช้ค่าของวัตถุชนิดใด ๆ ที่นอกเหนือจากvolatileหรือsig_atomic_tที่การรับสัญญาณ
  • ความพยายามที่จะแก้ไขสตริงตัวอักษรหรือวัตถุ const อื่น ๆ ในช่วงชีวิตของมัน
  • การต่อแคบที่มีสตริงตัวกว้างระหว่างการประมวลผลล่วงหน้า

ฟังก์ชั่นและแม่แบบ

  • ไม่ส่งคืนค่าจากฟังก์ชันการคืนค่า (โดยตรงหรือโดยการไหลออกจากลองบล็อก)
  • คำจำกัดความที่แตกต่างกันหลายอย่างสำหรับเอนทิตีเดียวกัน (คลาส, แม่แบบ, การแจกแจง, ฟังก์ชันอินไลน์, ฟังก์ชันสมาชิกแบบคงที่ ฯลฯ )
  • การเรียกซ้ำแบบไม่สิ้นสุดในการสร้างอินสแตนซ์ของเท็มเพลต
  • การเรียกใช้ฟังก์ชันโดยใช้พารามิเตอร์ที่แตกต่างกันหรือการเชื่อมโยงไปยังพารามิเตอร์และการเชื่อมโยงที่ฟังก์ชันถูกกำหนดให้เป็นการใช้

OOP

  • การทำลายล้างของวัตถุที่มีระยะเวลาการจัดเก็บแบบคงที่
  • ผลลัพธ์ของการกำหนดให้กับวัตถุที่ทับซ้อนกันบางส่วน
  • เข้าสู่ฟังก์ชันซ้ำในระหว่างการเริ่มต้นของวัตถุคงที่
  • การเรียกฟังก์ชันเสมือนไปยังฟังก์ชันเสมือนจริงของวัตถุจากตัวสร้างหรือตัวทำลาย
  • อ้างถึงสมาชิกของวัตถุที่ไม่ได้ถูกสร้างหรือถูกทำลายไปแล้ว

ไฟล์ต้นฉบับและการประมวลผลล่วงหน้า

  • ไฟล์ต้นฉบับที่ไม่ว่างเปล่าซึ่งไม่ได้ลงท้ายด้วยการขึ้นบรรทัดใหม่หรือลงท้ายด้วยแบ็กสแลช (ก่อนหน้า C ++ 11)
  • แบ็กสแลชตามด้วยอักขระที่ไม่ได้เป็นส่วนหนึ่งของรหัสหลบหนีที่ระบุในค่าคงที่ของอักขระหรือสตริง
  • เกินขีด จำกัด การนำไปใช้งาน (จำนวนบล็อกซ้อนกัน, จำนวนฟังก์ชันในโปรแกรม, พื้นที่สแต็กที่มีอยู่ ... )
  • ค่าตัวเลขของตัวประมวลผลล่วงหน้าที่ไม่สามารถแทนได้ long int
  • การประมวลผลคำสั่งล่วงหน้าที่ด้านซ้ายของนิยามแมโครที่เหมือนฟังก์ชัน
  • สร้างโทเค็นที่กำหนดแบบไดนามิกใน#ifนิพจน์

ที่จะจัดประเภท

  • การโทรออกระหว่างการทำลายโปรแกรมที่มีระยะเวลาจัดเก็บแบบคงที่

อืม ... NaN (x / 0) และ Infinity (0/0) ถูกปกคลุมด้วย IEE 754 ถ้า C ++ ได้รับการออกแบบในภายหลังทำไมมันถึงบันทึก x / 0 ว่าไม่ได้ถูกกำหนด?
new123456

Re: "แบ็กสแลชตามด้วยอักขระที่ไม่ได้เป็นส่วนหนึ่งของรหัสหลบหนีที่ระบุในค่าคงที่ของอักขระหรือสตริง นั่นคือ UB ใน C89 (§3.1.3.4) และ C ++ 03 (ซึ่งรวม C89) แต่ไม่ใช่ใน C99 C99 กล่าวว่า "ผลลัพธ์ไม่ใช่โทเค็นและจำเป็นต้องมีการวินิจฉัย" (§6.4.4.4) สันนิษฐานว่า C ++ 0x (ซึ่งรวม C89) จะเหมือนกัน
Adam Rosenfield

1
มาตรฐาน C99 มีรายการพฤติกรรมที่ไม่ได้กำหนดในภาคผนวก J.2 จะต้องใช้งานบ้างในการปรับรายการนี้เป็น C ++ คุณต้องเปลี่ยนการอ้างอิงไปยังคำสั่ง C ++ ที่ถูกต้องแทนที่จะเป็นคำสั่ง C99 ลบสิ่งที่ไม่เกี่ยวข้องออกและตรวจสอบว่าสิ่งเหล่านั้นทั้งหมดนั้นไม่ได้กำหนดจริงๆใน C ++ และ C แต่มันมีจุดเริ่มต้น
Steve Jessop

1
@ new123456 - ไม่ใช่หน่วยจุดลอยตัวทั้งหมดที่เข้ากันได้กับ IEE754 หาก C ++ ต้องการความสอดคล้องกับ IEE754 คอมไพเลอร์จะต้องทดสอบและจัดการเคสที่ RHS เป็นศูนย์ผ่านการตรวจสอบที่ชัดเจน โดยการทำให้พฤติกรรมไม่ได้กำหนดคอมไพเลอร์สามารถหลีกเลี่ยงค่าใช้จ่ายที่โดยพูดว่า "ถ้าคุณใช้ IEE754 FPU ที่ไม่ใช่คุณจะไม่ได้รับพฤติกรรม IEEE754 FPU"
SecurityMatt

1
"การประเมินค่านิพจน์ที่มีผลลัพธ์ไม่อยู่ในช่วงของประเภทที่สอดคล้องกัน" .... การล้นจำนวนเต็มมีการกำหนดอย่างดีสำหรับประเภทอินทิกรัล UNSIGNED ไม่ใช่แค่การเซ็นชื่อ
nacitar sevaht

31

ลำดับที่พารามิเตอร์ฟังก์ชั่นได้รับการประเมินเป็นที่ไม่ระบุพฤติกรรม (สิ่งนี้จะไม่ทำให้โปรแกรมของคุณขัดข้องระเบิดหรือสั่งพิซซ่า ... ไม่เหมือนกับพฤติกรรมที่ไม่ได้กำหนด )

ข้อกำหนดเพียงอย่างเดียวคือพารามิเตอร์ทั้งหมดจะต้องถูกประเมินอย่างสมบูรณ์ก่อนที่จะเรียกใช้ฟังก์ชัน


นี้:

// The simple obvious one.
callFunc(getA(),getB());

สามารถเทียบเท่ากับสิ่งนี้:

int a = getA();
int b = getB();
callFunc(a,b);

หรือสิ่งนี้:

int b = getB();
int a = getA();
callFunc(a,b);

มันสามารถเป็นได้ทั้ง; มันขึ้นอยู่กับคอมไพเลอร์ ผลที่ได้อาจมีความสำคัญขึ้นอยู่กับผลข้างเคียง


23
คำสั่งซื้อนั้นไม่ได้ระบุไม่ได้กำหนด
Rob Kennedy

1
ฉันเกลียดสิ่งนี้ :) ฉันสูญเสียวันทำงานทันทีที่ติดตามกรณีเหล่านี้ ... อย่างไรก็ตามเรียนรู้บทเรียนของฉันและไม่ได้โชคดีอีกครั้ง
Robert Gould

2
@Rob: ฉันจะเถียงกับคุณเกี่ยวกับการเปลี่ยนแปลงความหมายที่นี่ แต่ฉันรู้ว่าคณะกรรมการมาตรฐานนั้นค่อนข้างพิถีพิถันในคำจำกัดความที่แท้จริงของสองคำนี้ ดังนั้นผมก็จะเปลี่ยน :-)
มาร์ตินนิวยอร์ก

2
ฉันโชคดีในครั้งนี้ ฉันถูกมันกัดเมื่อฉันอยู่ในวิทยาลัยและมีอาจารย์คนหนึ่งที่ดูมันและบอกปัญหาของฉันในเวลาประมาณ 5 วินาที ไม่มีการบอกเวลาเท่าไรที่ฉันจะได้แก้จุดบกพร่องอย่างอื่น
Bill the Lizard

27

คอมไพเลอร์มีอิสระในการจัดลำดับส่วนการประเมินของนิพจน์อีกครั้ง (สมมติว่าความหมายไม่เปลี่ยนแปลง)

จากคำถามเดิม:

a[i] = i++;

// This expression has three parts:
(a) a[i]
(b) i++
(c) Assign (b) to (a)

// (c) is guaranteed to happen after (a) and (b)
// But (a) and (b) can be done in either order.
// See n2521 Section 5.17
// (b) increments i but returns the original value.
// See n2521 Section 5.2.6
// Thus this expression can be written as:

int rhs  = i++;
int lhs& = a[i];
lhs = rhs;

// or
int lhs& = a[i];
int rhs  = i++;
lhs = rhs;

ล็อคตรวจสอบอีกครั้ง และหนึ่งความผิดพลาดง่ายที่จะทำ

A* a = new A("plop");

// Looks simple enough.
// But this can be split into three parts.
(a) allocate Memory
(b) Call constructor
(c) Assign value to 'a'

// No problem here:
// The compiler is allowed to do this:
(a) allocate Memory
(c) Assign value to 'a'
(b) Call constructor.
// This is because the whole thing is between two sequence points.

// So what is the big deal.
// Simple Double checked lock. (I know there are many other problems with this).
if (a == null) // (Point B)
{
    Lock   lock(mutex);
    if (a == null)
    {
        a = new A("Plop");  // (Point A).
    }
}
a->doStuff();

// Think of this situation.
// Thread 1: Reaches point A. Executes (a)(c)
// Thread 1: Is about to do (b) and gets unscheduled.
// Thread 2: Reaches point B. It can now skip the if block
//           Remember (c) has been done thus 'a' is not NULL.
//           But the memory has not been initialized.
//           Thread 2 now executes doStuff() on an uninitialized variable.

// The solution to this problem is to move the assignment of 'a'
// To the other side of the sequence point.
if (a == null) // (Point B)
{
    Lock   lock(mutex);
    if (a == null)
    {
        A* tmp = new A("Plop");  // (Point A).
        a = tmp;
    }
}
a->doStuff();

// Of course there are still other problems because of C++ support for
// threads. But hopefully these are addresses in the next standard.

จุดลำดับคืออะไร
yesraaj


1
โอ้ ... น่ารังเกียจโดยเฉพาะอย่างยิ่งเมื่อฉันได้เห็นโครงสร้างที่แน่นอนที่แนะนำใน Java
Tom

โปรดทราบว่าคอมไพเลอร์บางตัวจะกำหนดพฤติกรรมในสถานการณ์นี้ ตัวอย่างเช่นใน VC ++ 2005+ ถ้า a มีความผันผวนหน่วยความจำที่ต้องการถูกตั้งค่าเพื่อป้องกันการเรียงลำดับคำสั่งใหม่เพื่อให้การล็อคแบบตรวจสอบซ้ำทำงานได้
Eclipse

Martin York: <i> // (c) รับประกันว่าจะเกิดขึ้นหลังจาก (a) และ (b) </i> ใช่หรือไม่ ในตัวอย่างนั้นเป็นที่ยอมรับว่าเป็นสถานการณ์เดียวที่มันจะมีความสำคัญหาก 'i' เป็นตัวแปรระเหยที่แมปกับการลงทะเบียนฮาร์ดแวร์และ [i] (ค่าเก่าของ 'i') เป็นนามแฝง แต่มี รับประกันได้ว่าการเพิ่มขึ้นจะเกิดขึ้นก่อนจุดลำดับหรือไม่
supercat

5

สิ่งที่ฉันชอบคือ "การเรียกซ้ำแบบไม่สิ้นสุดในการสร้างอินสแตนซ์ของเทมเพลต" เพราะฉันเชื่อว่ามันเป็นสิ่งเดียวที่พฤติกรรมที่ไม่ได้กำหนดเกิดขึ้นในเวลารวบรวม


ทำได้ก่อนหน้านี้ แต่ฉันไม่เห็นว่ามันไม่ได้กำหนด มันค่อนข้างชัดเจนว่าคุณกำลังทำ recursion ที่ไม่มีที่สิ้นสุดในภายหลัง
Robert Gould

ปัญหาคือคอมไพเลอร์ไม่สามารถตรวจสอบโค้ดของคุณและตัดสินใจได้อย่างแม่นยำว่ามันจะประสบกับการเรียกซ้ำไม่สิ้นสุดหรือไม่ มันเป็นตัวอย่างของปัญหาการหยุดชะงัก ดู: stackoverflow.com/questions/235984/…
Daniel Earwicker

ใช่มันเป็นปัญหาที่หยุดชะงักแน่นอน
Robert Gould

มันทำให้ระบบของฉันพังเพราะการแลกเปลี่ยนที่เกิดจากหน่วยความจำน้อยเกินไป
Johannes Schaub - litb

2
ค่าคงที่ของตัวประมวลผลล่วงหน้าที่ไม่พอดีกับ int ยังเป็นเวลารวบรวม
โจชัว

5

การกำหนดให้เป็นค่าคงที่หลังจากการดึงconstness โดยใช้const_cast<>:

const int i = 10; 
int *p =  const_cast<int*>( &i );
*p = 1234; //Undefined

5

นอกจากนี้ยังไม่ได้กำหนดพฤติกรรม , นอกจากนี้ยังมีที่น่ารังเกียจอย่างเท่าเทียมกันพฤติกรรมการดำเนินงานที่กำหนดไว้

พฤติกรรมที่ไม่ได้กำหนดเกิดขึ้นเมื่อโปรแกรมทำบางสิ่งผลลัพธ์ที่ไม่ได้ระบุโดยมาตรฐาน

พฤติกรรมที่กำหนดไว้ในการนำไปใช้คือการกระทำของโปรแกรมผลลัพธ์ที่ไม่ได้กำหนดโดยมาตรฐาน แต่ต้องใช้เอกสารใดในการนำไปปฏิบัติ ตัวอย่างคือ "ตัวอักษรหลายไบต์ตัวอักษร" จากคำถาม Stack Overflow มีตัวแปลภาษา C ที่ไม่สามารถคอมไพล์ได้หรือไม่ .

พฤติกรรมที่กำหนดโดยการนำไปปฏิบัติจะกัดคุณเมื่อคุณเริ่มพอร์ต (แต่การอัปเกรดเป็นเวอร์ชั่นใหม่ของคอมไพเลอร์ก็คือการย้าย!)


4

ตัวแปรอาจได้รับการปรับปรุงเพียงครั้งเดียวในนิพจน์ (โดยทางเทคนิคหนึ่งครั้งระหว่างจุดลำดับ)

int i =1;
i = ++i;

// Undefined. Assignment to 'i' twice in the same expression.

Infact อย่างน้อยหนึ่งครั้งระหว่างจุดลำดับสองจุด
Prasoon Saurav

2
@rasoon: ฉันคิดว่าคุณหมายถึง: มากที่สุดระหว่างจุดลำดับสองครั้ง :-)
Nawaz

3

ความเข้าใจพื้นฐานเกี่ยวกับข้อ จำกัด ด้านสิ่งแวดล้อมต่างๆ รายการทั้งหมดอยู่ในส่วน 5.2.4.1 ของข้อกำหนด C ที่นี่มีไม่กี่;

  • 127 พารามิเตอร์ในฟังก์ชันเดียว
  • 127 ข้อโต้แย้งในการเรียกใช้ฟังก์ชันเดียว
  • 127 พารามิเตอร์ในแมโครเดียว
  • 127 ข้อโต้แย้งในการเรียกใช้แมโครเดียว
  • 4095 ตัวอักษรในบรรทัดซอร์สโลจิคัล
  • 4095 ตัวอักษรในสตริงตัวอักษรตัวอักษรหรือสตริงกว้างตามตัวอักษร (หลังจากการต่อข้อมูล)
  • 65535 ไบต์ในวัตถุ (ในสภาพแวดล้อมที่โฮสต์เท่านั้น)
  • 15 ระดับที่น่ารังเกียจสำหรับ #included fi les
  • 1023 เลเบลเคสสำหรับคำสั่ง switch (ไม่รวมเลเบลสำหรับคำสั่ง switch ใด ๆ )

จริง ๆ แล้วฉันรู้สึกประหลาดใจเล็กน้อยที่ขีด จำกัด ของป้ายกำกับกรณี 1,023 รายการสำหรับคำสั่งเปลี่ยนฉันสามารถมองเห็นได้ว่าเกินความเป็นจริงสำหรับโค้ด / lex / parsers ที่สร้างขึ้นค่อนข้างง่าย

หากเกินขีด จำกัด เหล่านี้คุณมีพฤติกรรมที่ไม่ได้กำหนดไว้ (ขัดข้องข้อบกพร่องด้านความปลอดภัย ฯลฯ ... )

ใช่ฉันรู้ว่านี่มาจากข้อกำหนด C แต่ C ++ แบ่งปันการสนับสนุนพื้นฐานเหล่านี้


9
หากคุณถึงขีด จำกัด เหล่านี้แสดงว่าคุณมีปัญหามากกว่าพฤติกรรมที่ไม่ได้กำหนด
new123456

คุณสามารถทำได้อย่างง่ายดายเกิน 65535 ไบต์ในวัตถุเช่น STD :: vector
Demi

2

ใช้memcpyเพื่อคัดลอกระหว่างพื้นที่หน่วยความจำที่ทับซ้อนกัน ตัวอย่างเช่น:

char a[256] = {};
memcpy(a, a, sizeof(a));

พฤติกรรมนี้ไม่ได้กำหนดตามมาตรฐาน C ซึ่งถูกรวมไว้ด้วยมาตรฐาน C ++ 03

7.21.2.1 ฟังก์ชัน memcpy

สรุป

1 / # รวมโมฆะ * memcpy (โมฆะ * จำกัด s1, โมฆะ const * จำกัด s2, size_t n);

ลักษณะ

2 / ฟังก์ชั่น memcpy คัดลอกตัวละคร n จากวัตถุที่ชี้ไปโดย s2 ลงในวัตถุที่ชี้ไปโดย s1 หากการคัดลอกเกิดขึ้นระหว่างวัตถุที่ทับซ้อนกันพฤติกรรมจะไม่ได้กำหนดไว้ คืน 3 ฟังก์ชัน memcpy ส่งคืนค่า s1

7.21.2.2 ฟังก์ชั่น memmove

สรุป

1 #include void * memmove (void * s1, const void * s2, size_t n);

ลักษณะ

2 ฟังก์ชั่น memmove คัดลอกตัวละคร n จากวัตถุที่ชี้ไปโดย s2 ลงในวัตถุที่ชี้ไปโดย s1 การคัดลอกเกิดขึ้นราวกับว่าอักขระ n จากวัตถุที่ชี้ไปยัง s2 ถูกคัดลอกไปยังอาร์เรย์ชั่วคราวที่มีอักขระ n ตัวที่ไม่ทับซ้อนวัตถุที่ชี้ไปที่ s1 และ s2 จากนั้นคัดลอกอักขระ n จากอาร์เรย์ชั่วคราวลงใน วัตถุที่ชี้ไปโดย s1 ผลตอบแทน

3 ฟังก์ชัน memmove ส่งคืนค่า s1


2

ชนิดเดียวที่ c ++ charรับประกันขนาดคือ และขนาดคือ 1 ขนาดของประเภทอื่น ๆ ทั้งหมดขึ้นอยู่กับแพลตฟอร์ม


นั่นคือสิ่งที่ <cstdint> มีไว้เพื่ออะไร? มันกำหนดประเภทเช่น uint16_6 และ cetera
Jasper Bekkers

ใช่ แต่ขนาดของประเภทส่วนใหญ่พูดยาวไม่ได้กำหนดไว้อย่างชัดเจน
JaredPar

cstdint ยังไม่ได้เป็นส่วนหนึ่งของมาตรฐาน c ++ ปัจจุบัน ดูที่ boost / stdint.hpp สำหรับโซลูชั่นพกพาในปัจจุบัน
Evan Teran

นั่นไม่ใช่พฤติกรรมที่ไม่ได้กำหนด มาตรฐานบอกว่าแพลตฟอร์มที่สอดคล้องนั้นจะกำหนดขนาดแทนที่จะเป็นมาตรฐานที่กำหนดไว้
Daniel Earwicker

1
@JaredPar: มันเป็นโพสต์ที่ซับซ้อนที่มีจำนวนมากของหัวข้อของการสนทนาดังนั้นฉันสรุปมันทั้งหมดขึ้นที่นี่ บรรทัดล่างคือ: "5. ในการแทน -2147483647 และ +2147483647 ในไบนารีคุณต้องมี 32 บิต"
John Dibling

2

วัตถุระดับเนมสเปซในหน่วยการคอมไพล์ที่แตกต่างกันไม่ควรพึ่งพาซึ่งกันและกันสำหรับการเริ่มต้นเนื่องจากลำดับการเริ่มต้นของพวกเขาจะไม่ได้กำหนด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.