ตัวแปรคงที่ระดับฟังก์ชันจะได้รับการจัดสรร / เริ่มต้นเมื่อใด


91

ฉันค่อนข้างมั่นใจว่าตัวแปรที่ประกาศทั่วโลกได้รับการจัดสรร (และเริ่มต้นถ้ามี) ในเวลาเริ่มโปรแกรม

int globalgarbage;
unsigned int anumber = 42;

แต่สิ่งที่คงที่ที่กำหนดไว้ในฟังก์ชัน?

void doSomething()
{
  static bool globalish = true;
  // ...
}

globalishจัดสรรพื้นที่เมื่อใด ฉันคาดเดาเมื่อรายการเริ่มต้นขึ้น แต่มันเริ่มต้นแล้วด้วยหรือไม่? หรือเริ่มต้นเมื่อdoSomething()ถูกเรียกครั้งแรก?

คำตอบ:


93

ฉันสงสัยเกี่ยวกับเรื่องนี้ดังนั้นฉันจึงเขียนโปรแกรมทดสอบต่อไปนี้และรวบรวมด้วย g ++ เวอร์ชัน 4.1.2

include <iostream>
#include <string>

using namespace std;

class test
{
public:
        test(const char *name)
                : _name(name)
        {
                cout << _name << " created" << endl;
        }

        ~test()
        {
                cout << _name << " destroyed" << endl;
        }

        string _name;
};

test t("global variable");

void f()
{
        static test t("static variable");

        test t2("Local variable");

        cout << "Function executed" << endl;
}


int main()
{
        test t("local to main");

        cout << "Program start" << endl;

        f();

        cout << "Program end" << endl;
        return 0;
}

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

global variable created
local to main created
Program start
static variable created
Local variable created
Function executed
Local variable destroyed
Program end
local to main destroyed
static variable destroyed
global variable destroyed

31
เพื่อเป็นการชี้แจง: ตัวแปรคงที่จะเริ่มต้นในครั้งแรกที่การดำเนินการถึงการประกาศไม่ใช่เมื่อมีการเรียกใช้ฟังก์ชันที่มี หากคุณมีสแตติกที่จุดเริ่มต้นของฟังก์ชัน (เช่นในตัวอย่างของคุณ) สิ่งเหล่านี้จะเหมือนกัน แต่ไม่จำเป็น: เช่นถ้าคุณมี 'if (... ) {static MyClass x; ... } 'จากนั้น' x 'จะไม่เริ่มต้นที่ ALL ในระหว่างการเรียกใช้ฟังก์ชันนั้นครั้งแรกในกรณีที่เงื่อนไขของคำสั่ง if ประเมินว่าเป็นเท็จ
EvanED

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

ภาพประกอบที่สมบูรณ์แบบ
Des1gnWizard

@veio: ใช่การเริ่มต้นปลอดภัยต่อเธรด ดูคำถามสำหรับรายละเอียดเพิ่มเติม: stackoverflow.com/questions/23829389/…
Rémi

2
@HelloGoodbye: ใช่มันนำไปสู่ค่าใช้จ่ายรันไทม์ ดูคำถามนั้นด้วย: stackoverflow.com/questions/23829389/…
Rémi

54

คำฟุ่มเฟือยที่เกี่ยวข้องบางประการจากมาตรฐาน C ++:

3.6.2 การเริ่มต้นอ็อบเจ็กต์ที่ไม่ใช่โลคัล [basic.start.init]

1

การจัดเก็บสำหรับวัตถุที่มีระยะเวลาการจัดเก็บแบบคงที่ ( basic.stc.static ) จะต้องเริ่มต้นเป็นศูนย์ ( dcl.init ) ก่อนที่การเริ่มต้นอื่น ๆ จะเกิดขึ้น อ็อบเจ็กต์ประเภท POD ( basic.types ) ที่มีระยะเวลาการจัดเก็บแบบคงที่ซึ่งเริ่มต้นด้วยนิพจน์คงที่ ( expr.const ) จะถูกเตรียมใช้งานก่อนที่จะมีการเตรียมใช้งานแบบไดนามิก อ็อบเจ็กต์ของขอบเขตเนมสเปซที่มีระยะเวลาการจัดเก็บแบบคงที่ซึ่งกำหนดไว้ในหน่วยการแปลเดียวกันและการเตรียมใช้งานแบบไดนามิกจะถูกเตรียมใช้งานตามลำดับที่คำจำกัดความปรากฏในหน่วยการแปล [หมายเหตุ: dcl.init.aggr อธิบายลำดับการเริ่มต้นสมาชิกรวม การเริ่มต้นของวัตถุคงท้องถิ่นอธิบายไว้ในstmt.dcl ]

[ข้อความเพิ่มเติมด้านล่างเพิ่มเสรีภาพสำหรับผู้เขียนคอมไพเลอร์]

6.7 ประกาศคำสั่ง [stmt.dcl]

...

4

การเริ่มต้นเป็นศูนย์ ( dcl.init ) ของอ็อบเจ็กต์โลคัลทั้งหมดที่มีระยะเวลาการจัดเก็บแบบคงที่ ( basic.stc.static ) จะดำเนินการก่อนที่จะเริ่มต้นอื่น ๆ อ็อบเจ็กต์โลคัลประเภท POD ( basic.types ) ที่มีระยะเวลาการจัดเก็บแบบคงที่ที่เริ่มต้นด้วยนิพจน์คงที่จะถูกเตรียมใช้งานก่อนที่จะป้อนบล็อกก่อน การใช้งานได้รับอนุญาตให้ทำการกำหนดค่าเริ่มต้นของอ็อบเจ็กต์โลคัลอื่น ๆ ก่อนโดยมีระยะเวลาการจัดเก็บแบบคงที่ภายใต้เงื่อนไขเดียวกับที่การใช้งานได้รับอนุญาตให้เตรียมใช้งานอ็อบเจ็กต์แบบคงที่ด้วยระยะเวลาการจัดเก็บแบบคงที่ในขอบเขตเนมสเปซ ( basic.start.init). มิฉะนั้นวัตถุดังกล่าวจะเริ่มต้นเมื่อการควบคุมครั้งแรกผ่านการประกาศ วัตถุดังกล่าวถือว่าเริ่มต้นเมื่อการเริ่มต้นเสร็จสมบูรณ์ หากการเริ่มต้นออกจากข้อยกเว้นการกำหนดค่าเริ่มต้นจะไม่สมบูรณ์ดังนั้นจะพยายามอีกครั้งในครั้งต่อไปที่การควบคุมเข้าสู่การประกาศ หากการควบคุมเข้าสู่การประกาศซ้ำ (เรียกซ้ำ) ในขณะที่วัตถุกำลังเริ่มต้นพฤติกรรมจะไม่ได้กำหนด [ ตัวอย่าง:

      int foo(int i)
      {
          static int s = foo(2*i);  // recursive call - undefined
          return i+1;
      }

- ตัวอย่างตอนท้าย ]

5

ตัวทำลายสำหรับอ็อบเจ็กต์โลคัลที่มีระยะเวลาการจัดเก็บแบบคงที่จะดำเนินการต่อเมื่อตัวแปรถูกสร้างขึ้น [หมายเหตุ: basic.start.term อธิบายลำดับที่วัตถุโลคัลที่มีระยะเวลาการจัดเก็บแบบคงที่ถูกทำลาย ]


สิ่งนี้ตอบคำถามของฉันและไม่ได้อาศัย "หลักฐานเชิงประวัติ" ซึ่งแตกต่างจากคำตอบที่ยอมรับ ฉันกำลังมองหาการกล่าวถึงข้อยกเว้นในตัวสร้างของวัตถุคงที่ในพื้นที่ของฟังก์ชันเริ่มต้นแบบคงที่เริ่มต้นโดยเฉพาะ:If the initialization exits by throwing an exception, the initialization is not complete, so it will be tried again the next time control enters the declaration.
Bensge

26

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


2
ไม่มากนักสถิติในพื้นที่จะถูกจัดสรรและเป็นศูนย์เริ่มต้น "ที่โหลดโปรแกรม" (ในเครื่องหมายคำพูดเพราะไม่ถูกต้องเช่นกัน) จากนั้นกำหนดค่าเริ่มต้นใหม่ในครั้งแรกที่มีการป้อนฟังก์ชัน
หมูปิ้ง

ดูเหมือนว่าลิงก์นั้นจะใช้งานไม่ได้ใน 7 ปีต่อมา
สตีฟ

1
ใช่ลิงค์พัง นี่คือที่เก็บถาวร: web.archive.org/web/20100328062506/http://www.acm.org/…
Eugene

10

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

@Adam: เบื้องหลังการฉีดโค้ดโดยคอมไพเลอร์คือเหตุผลของผลลัพธ์ที่คุณเห็น


5

ฉันลองทดสอบโค้ดอีกครั้งจากAdam Pierceและเพิ่มอีกสองกรณี: ตัวแปรคงที่ในคลาสและประเภท POD คอมไพเลอร์ของฉันคือ g ++ 4.8.1 ใน Windows OS (MinGW-32) ผลลัพธ์เป็นตัวแปรคงที่ในคลาสจะถือว่าเหมือนกันกับตัวแปรส่วนกลาง ตัวสร้างจะถูกเรียกก่อนเข้าสู่ฟังก์ชันหลัก

  • สรุป (สำหรับ g ++, สภาพแวดล้อม Windows):

    1. ตัวแปรทั่วโลกและสมาชิกแบบคงที่ในระดับ : คอนสตรัคเรียกว่าก่อนที่จะเข้าสู่หลักฟังก์ชั่น(1)
    2. ตัวแปรคงที่ภายใน : ตัวสร้างจะถูกเรียกเมื่อการดำเนินการถึงการประกาศในครั้งแรกเท่านั้น
    3. หากตัวแปรคงที่ท้องถิ่นเป็นประเภท PODแล้วก็ยังเริ่มต้นก่อนที่จะเข้าสู่หลักฟังก์ชั่น(1) ตัวอย่างสำหรับประเภท POD: หมายเลข int คงที่ = 10;

(1) : สถานะที่ถูกต้องควรเป็น: "ก่อนเรียกฟังก์ชันใด ๆ จากหน่วยการแปลเดียวกัน" อย่างไรก็ตามพูดง่ายๆตามตัวอย่างด้านล่างนี้ก็คือหน้าที่หลัก

รวม <iostream>

#include < string>

using namespace std;

class test
{
public:
   test(const char *name)
            : _name(name)
    {
            cout << _name << " created" << endl;
    }

    ~test()
    {
            cout << _name << " destroyed" << endl;
    }

    string _name;
    static test t; // static member
 };
test test::t("static in class");

test t("global variable");

void f()
{
    static  test t("static variable");
    static int num = 10 ; // POD type, init before enter main function

    test t2("Local variable");
    cout << "Function executed" << endl;
}

int main()
{
    test t("local to main");
    cout << "Program start" << endl;
    f();
    cout << "Program end" << endl;
    return 0;
 }

ผลลัพธ์:

static in class created
global variable created
local to main created
Program start
static variable created
Local variable created
Function executed
Local variable destroyed
Program end
local to main destroyed
static variable destroyed
global variable destroyed
static in class destroyed

มีใครทดสอบใน Linux env บ้าง?


4

หรือเริ่มต้นเมื่อ doSomething () ถูกเรียกครั้งแรก?

ใช่แล้ว. สิ่งนี้ช่วยให้คุณเริ่มต้นโครงสร้างข้อมูลที่เข้าถึงได้ทั่วโลกเมื่อมีความเหมาะสมตัวอย่างเช่นภายในบล็อก try / catch เช่นแทนที่จะเป็น

int foo = init(); // bad if init() throws something

int main() {
  try {
    ...
  }
  catch(...){
    ...
  }
}

คุณสามารถเขียน

int& foo() {
  static int myfoo = init();
  return myfoo;
}

และใช้ในบล็อก try / catch ในการโทรครั้งแรกตัวแปรจะเริ่มต้น จากนั้นในการโทรครั้งแรกและครั้งถัดไปค่าของมันจะถูกส่งกลับ (โดยการอ้างอิง)


3

ตัวแปรแบบคงที่จะถูกจัดสรรภายในส่วนของโค้ดซึ่งเป็นส่วนหนึ่งของอิมเมจที่เรียกใช้งานได้และมีการแมปในการเริ่มต้นแล้ว

ตัวแปรคงที่ภายในขอบเขตฟังก์ชันจะได้รับการปฏิบัติเหมือนกันขอบเขตเป็นโครงสร้างระดับภาษาเท่านั้น

ด้วยเหตุนี้คุณจึงรับประกันได้ว่าตัวแปรคงที่จะเริ่มต้นเป็น 0 (เว้นแต่คุณจะระบุอย่างอื่น) แทนที่จะเป็นค่าที่ไม่ได้กำหนด

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

ใน C ++ (ขอบเขตทั่วโลก) คงมีตัวสร้างที่เรียกว่าเป็นส่วนหนึ่งของโปรแกรมเริ่มทำงานภายใต้การควบคุมของไลบรารีรันไทม์ C ภายใต้ Visual C ++ อย่างน้อยลำดับที่อ็อบเจ็กต์ถูกเตรียมใช้งานสามารถถูกควบคุมโดยinit_seg pragma


4
คำถามนี้เกี่ยวกับสถิติที่กำหนดขอบเขตฟังก์ชัน อย่างน้อยเมื่อพวกเขามีตัวสร้างที่ไม่สำคัญพวกเขาจะเริ่มต้นเมื่อเข้าสู่ฟังก์ชันครั้งแรก หรือโดยเฉพาะอย่างยิ่งเมื่อถึงบรรทัดนั้น
Adam Mitz

จริง - แต่คำถามพูดถึงพื้นที่ที่จัดสรรให้กับตัวแปรและใช้ชนิดข้อมูลง่ายๆ ยังคงจัดสรรพื้นที่ในส่วนรหัส
Rob Walker

ฉันไม่เห็นว่ากลุ่มโค้ดกับกลุ่มข้อมูลมีความสำคัญอย่างไรที่นี่ ฉันคิดว่าเราต้องการคำชี้แจงจาก OP เขาบอกว่า "และเริ่มต้นถ้ามี"
Adam Mitz

5
ตัวแปรจะไม่ถูกจัดสรรภายในส่วนของรหัส ด้วยวิธีนี้พวกเขาจะไม่สามารถเขียนได้
botismarius

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