ตัวแปรคงที่ภายในฟังก์ชันใน C


119

จะพิมพ์อะไรออกมา 6 6 หรือ 6 7? และทำไม?

void foo()
{
    static int x = 5;
    x++;
    printf("%d", x);
}

int main()
{
    foo();
    foo();
    return 0;
}

54
มีปัญหาอะไรให้ลองทำ
Andrew

12
คุณลองพิมพ์สิ่งนี้ดูด้วยตัวคุณเองหรือไม่?
wilhelmtell

21
ฉันอยากเข้าใจว่าทำไม
Vadiklk

7
@Vadiklk ถามคำถามที่ขึ้นต้นด้วย "ทำไม"
Andrey

1
ideone.com/t9Bbeคุณคาดหวังอะไร? ผลลัพธ์ไม่ตรงกับการเปิดเผยของคุณหรือไม่? ทำไมคุณถึงคาดหวังผลลัพธ์ของคุณ?
eckes

คำตอบ:


187

มีสองประเด็นที่นี่อายุการใช้งานและขอบเขต

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

อายุการใช้งานของตัวแปรคือช่วงเวลาที่มีอยู่ ถ้า x ถูกกำหนดโดยไม่มีคีย์เวิร์ด static อายุการใช้งานจะมาจากการป้อน foo () ไปจนถึงผลตอบแทนจาก foo (); ดังนั้นจะเริ่มต้นใหม่เป็น 5 ทุกครั้งที่โทร

คำหลักคงทำหน้าที่ขยายอายุการใช้งานของตัวแปรไปจนถึงอายุการใช้งานของโปรแกรม เช่นการเริ่มต้นเกิดขึ้นครั้งเดียวและครั้งเดียวจากนั้นตัวแปรจะยังคงรักษาค่าไว้ไม่ว่าจะเป็นอะไรก็ตาม - ในอนาคตทั้งหมดที่เรียกไปที่ foo ()


15
@devanl ใช่เรา
orion elenzil

1
ง่ายและมีเหตุผล :)
Dimitar Vukman

ในสถานการณ์ใดบ้างที่เราต้องประกาศตัวแปรเป็นแบบคงที่ภายในฟังก์ชันเพียงแค่อยากรู้ว่าฉันไม่เคยใช้สิ่งนี้มาก่อน
Akay

ฉันจะขอบคุณ แต่ทั้งหมดนี้ได้รับคำตอบที่ด้านบนสุดของหน้า มันทำให้ฉันหัวเราะที่ผู้คนไม่เพียง แต่เรียกใช้โค้ดของตัวเอง xD
Puddle

คำตอบนี้ผิด ช่วงเวลาที่คุณคิดถึงฟังก์ชันวนซ้ำคำจำกัดความที่อธิบายไว้ที่นี่ไม่ได้อธิบายพฤติกรรม!
Philip Couling

53

เอาต์พุต : 6 7

เหตุผล : ตัวแปรคงถูกเริ่มต้นเพียงครั้งเดียว (ไม่เหมือนกับตัวแปรอัตโนมัติ) และคำจำกัดความเพิ่มเติมของตัวแปรคงที่จะถูกข้ามระหว่างรันไทม์ และหากไม่ได้เริ่มต้นด้วยตนเองระบบจะเริ่มต้นด้วยค่า 0 โดยอัตโนมัติ ดังนั้น,

void foo() {
    static int x = 5; // assigns value of 5 only once
    x++;
    printf("%d", x);
}

int main() {
    foo(); // x = 6
    foo(); // x = 7
    return 0;
}

10

6 7

คอมไพเลอร์จัดเรียงว่าการเริ่มต้นตัวแปรแบบคงที่จะไม่เกิดขึ้นทุกครั้งที่ป้อนฟังก์ชัน


10

เช่นเดียวกับการมีโปรแกรมต่อไปนี้:

static int x = 5;

void foo()
{
    x++;
    printf("%d", x);
}

int main()
{
     foo();
     foo();
     return 0;
}

สิ่งที่คีย์เวิร์ดแบบคงที่ทำในโปรแกรมนั้นคือมันบอกคอมไพเลอร์ (โดยพื้นฐาน) ว่า 'เฮ้ฉันมีตัวแปรที่นี่ฉันไม่ต้องการให้ใครเข้าถึงอย่าบอกคนอื่นว่ามีอยู่'

ภายในเมธอดคีย์เวิร์ดแบบคงที่จะบอกคอมไพลเลอร์เช่นเดียวกับด้านบน แต่อย่าบอกใครว่าสิ่งนี้มีอยู่นอกฟังก์ชันนี้ควรเข้าถึงได้ภายในฟังก์ชันนี้เท่านั้น

ฉันหวังว่านี่จะช่วยได้


13
มันไม่เหมือนกันจริงๆ ยังมีปัญหาเรื่องขอบเขตของ X ในตัวอย่างนี้คุณสามารถโผล่และ futz ด้วยxใน main; เป็นระดับโลก ในตัวอย่างเดิมxคือ foo ในพื้นที่ซึ่งมองเห็นได้เฉพาะในขณะที่อยู่ในบล็อกนั้นซึ่งโดยทั่วไปนิยมใช้: ถ้า foo มีอยู่เพื่อรักษาxในรูปแบบที่คาดเดาได้และมองเห็นได้การปล่อยให้คนอื่นแหย่โดยทั่วไปจะเป็นอันตราย เป็นประโยชน์อีกประการหนึ่งของการรักษาขอบเขตfoo() นอกจากนี้ยังช่วยให้foo()พกพา
user2149140

2
@ user2149140 'อย่าบอกใครว่ามีอยู่นอกฟังก์ชั่นนี้ควรเข้าถึงได้จากฟังก์ชันนี้เท่านั้น'
DCShannon

3
ในขณะที่คุณได้แก้ไขปัญหาของขอบเขตเนื่องจากการประกาศตัวแปรคำอธิบายของคงที่ว่ามีผลต่อขอบเขตดูเหมือนจะไม่ถูกต้อง
DCShannon

1
@Chameleon คำถามถูกแท็กเป็นcดังนั้นในบริบทนี้ตัวอย่างของคุณจะผิดกฎหมายในขอบเขตทั่วโลก (C ต้องการตัวเริ่มต้นคงที่สำหรับ globals, C ++ ไม่ได้)
Richard J.Ross III

5

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


การบอกว่านี่เป็นเหมือนตัวแปร "global" แล้วบอกว่า EXCEPT you can't access it is an oxymoron Global หมายถึงสามารถเข้าถึงได้ทุกที่ ซึ่งในกรณีของฟังก์ชัน INSIDE แบบคงที่นี้จะไม่สามารถเข้าถึงได้ทุกที่ ปัญหาใน OP ตามที่คนอื่นตั้งข้อสังเกตคือเรื่องขอบเขตและอายุการใช้งาน โปรดอย่าสับสนกับคนที่ใช้คำว่า 'global' และทำให้เข้าใจผิดเกี่ยวกับขอบเขตของตัวแปร
ChuckB

@ChuckB: ถูกต้อง. ซ่อมมัน. เป็นเวลา 6 ปีแล้ว คำตอบก่อนหน้าของฉันมีการรับรู้เมื่อ 6 ปีก่อน!
Donotalo

5

เอาท์พุต: 6,7

เหตุผล

การประกาศxอยู่ภายในfooแต่การx=5เริ่มต้นเกิดขึ้นนอกfoo!

สิ่งที่เราต้องทำความเข้าใจก็คือ

static int x = 5;

ไม่เหมือนกับ

static int x;
x = 5;

คำตอบอื่น ๆ ได้ใช้คำสำคัญที่นี่ขอบเขตและอายุการใช้งานและชี้ให้เห็นว่าขอบเขตxมาจากจุดที่ประกาศในฟังก์ชันfoofooไปยังจุดสิ้นสุดของการทำงาน ตัวอย่างเช่นฉันตรวจสอบโดยย้ายการประกาศไปที่จุดสิ้นสุดของฟังก์ชันและนั่นทำให้xไม่ได้ประกาศในx++;คำสั่ง

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

อย่างไรก็ตามส่วนx = 5(อายุการใช้งาน) ของคำสั่งคือการเริ่มต้นของตัวแปรและเกิดขึ้นภายนอกของฟังก์ชันซึ่งเป็นส่วนหนึ่งของการโหลดโปรแกรม ตัวแปรxเกิดพร้อมกับค่า5เมื่อโปรแกรมโหลด

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

ค่า 5 ถูกตั้งค่าตามทฤษฎีไม่ว่าจะเรียก foo หรือไม่ก็ตามแม้ว่าคอมไพเลอร์อาจปรับฟังก์ชันให้เหมาะสมที่สุดหากคุณไม่ได้เรียกใช้ที่ใดก็ตาม ค่า 5 ควรอยู่ในตัวแปรก่อนที่จะเรียก foo

ด้านในของ fooแถลงการณ์static int x = 5;ไม่น่าจะสร้างรหัสใด ๆ เลย

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

Break Point ก่อนโทรครั้งแรกถึง foo


2

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


1
คุณแน่ใจหรือไม่ว่าสแตติกถูกเริ่มต้นก่อนที่จะมีการเรียกใช้ฟังก์ชันไม่ใช่เมื่อเรียกฟังก์ชันครั้งแรก
Jesse Pepper

@JessePepper: อย่างน้อยถ้าหน่วยความจำทำงานขึ้นอยู่กับว่าคุณกำลังพูดถึง C ++ 98/03 หรือ C ++ 11 ใน C ++ 98/03 ฉันเชื่อว่าเป็นไปตามที่อธิบายไว้ข้างต้น ใน C ++ 11 เธรดทำให้เป็นไปไม่ได้ที่จะทำโดยพื้นฐานแล้วการเริ่มต้นจะทำในรายการแรกของฟังก์ชัน
Jerry Coffin

2
ฉันคิดว่าคุณผิดจริง ฉันคิดว่าแม้ก่อน C ++ 11 จะเริ่มต้นเมื่อมีการเรียกใช้ฟังก์ชันเท่านั้น นี่เป็นสิ่งสำคัญสำหรับวิธีแก้ปัญหาทั่วไปสำหรับปัญหาการพึ่งพาการเริ่มต้นแบบคงที่
Jesse Pepper

2

Vadiklk,

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

main()
{
   static int var = 5;
   printf("%d ",var--);
   if(var)
      main();
} 

และคำตอบคือ 5 4 3 2 1 ไม่ใช่ 5 5 5 5 5 5 .... (ลูปไม่มีที่สิ้นสุด) อย่างที่คุณคาดหวัง อีกครั้งเหตุผลคือตัวแปรคงที่จะเริ่มต้นครั้งเดียวเมื่อครั้งต่อไป main () ถูกเรียกว่าจะไม่เริ่มต้นเป็น 5 เนื่องจากเริ่มต้นในโปรแกรมแล้วดังนั้นเราสามารถเปลี่ยนค่าได้ แต่ไม่สามารถเริ่มต้นใหม่ได้ นั่นคือวิธีการทำงานของตัวแปรคงที่

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

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

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

ขอบคุณ Javed


1

มาอ่านบทความ Wikipedia เรื่อง Static Variables ...

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


5
แย่มาก! "ตัวแปรที่ประกาศว่าคงที่ภายในฟังก์ชันจะถูกจัดสรรแบบคงที่" - มันไม่ได้อธิบายอะไรเลยเว้นแต่คุณจะรู้อยู่แล้วว่ามันหมายถึงอะไร!

@ แบล็งค์: นั่นคือสิ่งที่ฉันคิดว่าประโยคที่สองมีไว้เพื่อ แม้ว่าฉันเดาว่าคุณพูดถูก แต่ควรใช้คำที่ดีกว่านี้
Andrew White

นอกจากนี้สิ่งนี้ไม่ได้กล่าวถึงส่วนที่สับสนจริงๆซึ่งเป็นความจริงที่ว่าตัวเริ่มต้นถูกข้ามไปในการโทรครั้งต่อ ๆ ไป
Tom Auger

การจัดสรรแบบคงที่หมายถึงไม่มีสแต็กหรือฮีป
Chameleon

1

คุณจะได้รับ 6 7 พิมพ์ตามที่ทดสอบได้ง่ายและนี่คือเหตุผล: เมื่อใด fooใดที่ถูกเรียกครั้งแรกตัวแปรคงที่ x จะเริ่มต้นเป็น 5 จากนั้นจะเพิ่มขึ้นเป็น 6 และพิมพ์

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


1
6 7

x เป็นตัวแปรส่วนกลางที่มองเห็นได้จาก foo () เท่านั้น 5 คือค่าเริ่มต้นตามที่เก็บไว้ในส่วน. data ของรหัส การแก้ไขในภายหลังจะเขียนทับค่าก่อนหน้า ไม่มีการสร้างรหัสการกำหนดในเนื้อหาฟังก์ชัน


1

6 และ 7 เนื่องจากตัวแปรคงที่เริ่มต้นเพียงครั้งเดียวดังนั้น 5 ++ จึงกลายเป็น 6 ในการโทรครั้งที่ 1 6 ++ กลายเป็น 7 ในการเรียกครั้งที่ 2 หมายเหตุ - เมื่อการเรียกครั้งที่ 2 เกิดขึ้นจะใช้ค่า x เป็น 6 แทน 5 เนื่องจาก x เป็นตัวแปรคงที่


0

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

void f( int p )
{
  static const int first_p = p ;
  cout << "first p == " << p << endl ;
}

void main()
{
   f(1); f(2); f(3);
}

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

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