การประกาศตัวแปรในไฟล์ส่วนหัว - คงที่หรือไม่?


91

เมื่อปรับโครงสร้างใหม่บางส่วน#definesฉันเจอการประกาศที่คล้ายกับสิ่งต่อไปนี้ในไฟล์ส่วนหัว C ++:

static const unsigned int VAL = 42;
const unsigned int ANOTHER_VAL = 37;

คำถามคือสถิตจะสร้างความแตกต่างอะไรได้บ้าง? โปรดทราบว่าการรวมส่วนหัวหลายรายการไม่สามารถทำได้เนื่องจาก#ifndef HEADER #define HEADER #endifเคล็ดลับแบบคลาสสิก(หากมีความสำคัญ)

สแตติกหมายถึงการVALสร้างสำเนาเพียงชุดเดียวในกรณีที่ส่วนหัวรวมอยู่ในไฟล์ต้นฉบับมากกว่าหนึ่งไฟล์หรือไม่


คำตอบ:


107

staticหมายถึงว่าจะมีสำเนาของVALสร้างขึ้นสำหรับแต่ละแฟ้มแหล่งที่มามันจะรวมอยู่ใน. แต่ก็ยังหมายถึงการรวมหลาย ๆ จะไม่ส่งผลในหลายคำจำกัดความของคำVALที่จะชนกันในเวลาที่การเชื่อมโยง ใน C โดยไม่ต้องstaticคุณจะต้องมั่นใจว่าไฟล์เพียงหนึ่งแหล่งที่กำหนดไว้VALในขณะที่ไฟล์ที่มาอื่น ๆ externประกาศว่า โดยปกติแล้วเราจะทำได้โดยการกำหนด (อาจใช้ initializer) ในไฟล์ต้นฉบับและใส่การexternประกาศในไฟล์ส่วนหัว

static ตัวแปรในระดับโลกจะมองเห็นได้เฉพาะในซอร์สไฟล์ของตัวเองเท่านั้นไม่ว่าจะไปที่นั่นผ่านการรวมหรืออยู่ในไฟล์หลัก


หมายเหตุบรรณาธิการ:ใน C ++, constวัตถุที่มีทั้งstaticมิได้คำหลักในการประกาศของพวกเขาโดยปริยายexternstatic


ฉันเป็นแฟนของประโยคสุดท้ายซึ่งเป็นประโยชน์อย่างเหลือเชื่อ ฉันไม่ได้โหวตคำตอบเพราะ 42 ดีกว่า แก้ไข: ไวยากรณ์
RealDeal_EE'18

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

4
@ Brent212 คอมไพเลอร์ไม่ทราบว่าการประกาศ / คำจำกัดความมาจากไฟล์ส่วนหัวหรือไฟล์หลัก ดังนั้นคุณหวังอย่างไร้ประโยชน์ จะมีสำเนาของ VAL สองชุดหากมีคนโง่และใส่คำจำกัดความแบบคงที่ในไฟล์ส่วนหัวและรวมอยู่ในแหล่งข้อมูลสองแหล่ง
Justsalt

1
ค่า const มีการเชื่อมโยงภายในใน C ++
adrianN

112

staticและexternแท็กบนตัวแปรไฟล์ขอบเขตการตรวจสอบว่าพวกเขาจะสามารถเข้าถึงได้ในหน่วยการแปลอื่น ๆ (เช่นอื่น ๆ.cหรือ.cppไฟล์)

  • staticให้การเชื่อมโยงภายในของตัวแปรโดยซ่อนจากหน่วยการแปลอื่น ๆ อย่างไรก็ตามตัวแปรที่มีการเชื่อมโยงภายในสามารถกำหนดได้ในหน่วยการแปลหลายหน่วย

  • externให้การเชื่อมโยงภายนอกของตัวแปรทำให้หน่วยการแปลอื่นมองเห็นได้ โดยทั่วไปหมายความว่าต้องกำหนดตัวแปรในหน่วยการแปลเดียวเท่านั้น

ค่าเริ่มต้น (เมื่อคุณไม่ได้ระบุstaticหรือextern) คือหนึ่งในพื้นที่ที่ C และ C ++ แตกต่างกัน

  • ใน C ตัวแปรขอบเขตไฟล์คือextern(ลิงก์ภายนอก) ตามค่าเริ่มต้น หากคุณกำลังใช้ C, VALเป็นstaticและเป็นANOTHER_VALextern

  • ใน C ++ ตัวแปรที่กำหนดขอบเขตไฟล์คือstatic(การเชื่อมโยงภายใน) โดยค่าเริ่มต้นหากเป็นconstและexternโดยค่าเริ่มต้นหากไม่ใช่ หากคุณกำลังใช้ c ++ ทั้งสองVALและมีANOTHER_VALstatic

จากร่างข้อกำหนด C :

6.2.2 การเชื่อมโยงของตัวบ่งชี้ ... -5- ถ้าการประกาศตัวระบุสำหรับฟังก์ชันไม่มีตัวระบุคลาสหน่วยเก็บข้อมูลการเชื่อมโยงจะถูกกำหนดอย่างตรงไปตรงมาราวกับว่ามีการประกาศด้วย extern ตัวระบุคลาสหน่วยเก็บข้อมูล หากการประกาศตัวระบุสำหรับอ็อบเจ็กต์มีขอบเขตไฟล์และไม่มีตัวระบุคลาสหน่วยเก็บข้อมูลการเชื่อมโยงจะเป็นภายนอก

จากร่างข้อกำหนดC ++ :

7.1.1 - ตัวระบุคลาสหน่วยเก็บข้อมูล [dcl.stc] ... -6- ชื่อที่ประกาศในขอบเขตเนมสเปซโดยไม่มีตัวระบุคลาสหน่วยเก็บจะมีการเชื่อมโยงภายนอกเว้นแต่จะมีการเชื่อมโยงภายในเนื่องจากการประกาศก่อนหน้านี้และหากไม่มี ประกาศ const. วัตถุที่ประกาศ const และไม่ได้ประกาศอย่างชัดเจนจากภายนอกมีการเชื่อมโยงภายใน


47

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

test.h:

static int TEST = 0;
void test();

test1.cpp:

#include <iostream>
#include "test.h"

int main(void) {
    std::cout << &TEST << std::endl;
    test();
}

test2.cpp:

#include <iostream>
#include "test.h"

void test() {
    std::cout << &TEST << std::endl;
}

การรันสิ่งนี้ทำให้คุณได้ผลลัพธ์นี้:

0x446020
0x446040


5
ขอบคุณสำหรับตัวอย่าง!
Kyrol

ฉันสงสัยว่าTESTมีconstถ้า LTO จะสามารถเพิ่มประสิทธิภาพของมันเป็นที่ตั้งของหน่วยความจำที่เดียว แต่-O3 -fltoของ GCC 8.1 ไม่ได้
Ciro Santilli 郝海东冠状病六四事件法轮功

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

6

constตัวแปรใน C ++ มีการเชื่อมโยงภายใน ดังนั้นการใช้staticจึงไม่มีผล

อา

const int i = 10;

one.cpp

#include "a.h"

func()
{
   cout << i;
}

สอง.cpp

#include "a.h"

func1()
{
   cout << i;
}

หากนี่เป็นโปรแกรม C คุณจะได้รับข้อผิดพลาด 'คำจำกัดความหลายรายการ' สำหรับi(เนื่องจากการเชื่อมโยงภายนอก)


2
การใช้staticมีผลที่จะส่งสัญญาณเจตนาและการรับรู้ถึงสิ่งที่กำลังเข้ารหัสซึ่งไม่ใช่เรื่องเลวร้าย สำหรับฉันแล้วสิ่งนี้ก็เหมือนกับการรวมvirtualเมื่อลบล้าง: เราไม่จำเป็นต้องทำ แต่สิ่งต่าง ๆ ดูง่ายกว่ามาก - และสอดคล้องกับการประกาศอื่น ๆ - เมื่อเราทำ
underscore_d

คุณอาจได้รับข้อผิดพลาดหลายนิยามใน C ซึ่งเป็นพฤติกรรมที่ไม่ได้กำหนดโดยไม่จำเป็นต้องมีการวินิจฉัย
MM

5

การประกาศแบบคงที่ที่โค้ดระดับนี้หมายความว่าตัวแปรจะปรากฏในหน่วยคอมไพล์ปัจจุบันเท่านั้น ซึ่งหมายความว่ามีเพียงโค้ดภายในโมดูลนั้นเท่านั้นที่จะเห็นตัวแปรนั้น

หากคุณมีไฟล์ส่วนหัวที่ประกาศตัวแปรคงที่และส่วนหัวนั้นรวมอยู่ในไฟล์ C / CPP หลายไฟล์ตัวแปรนั้นจะเป็น "โลคัล" สำหรับโมดูลเหล่านั้น จะมี N สำเนาของตัวแปรนั้นสำหรับ N ตำแหน่งที่รวมส่วนหัวไว้ พวกเขาไม่เกี่ยวข้องกันเลย โค้ดใด ๆ ภายในซอร์สไฟล์ใด ๆ เหล่านั้นจะอ้างอิงเฉพาะตัวแปรที่ประกาศภายในโมดูลนั้น

ในกรณีนี้คำหลัก "คงที่" ดูเหมือนจะไม่ให้ประโยชน์ใด ๆ ฉันอาจจะขาดอะไรไป แต่ดูเหมือนจะไม่สำคัญ - ฉันไม่เคยเห็นอะไรแบบนี้มาก่อน

สำหรับอินไลน์ในกรณีนี้ตัวแปรมีแนวโน้มที่จะอินไลน์ แต่นั่นเป็นเพียงเพราะมีการประกาศ const คอมไพเลอร์อาจมีแนวโน้มที่จะอินไลน์โมดูลตัวแปรคงที่ แต่ขึ้นอยู่กับสถานการณ์และโค้ดที่กำลังคอมไพล์ ไม่มีการรับประกันว่าคอมไพเลอร์จะอินไลน์ 'สถิต'


ประโยชน์ของ 'คงที่' ที่นี่คือมิฉะนั้นคุณจะประกาศหลาย globals ที่มีชื่อเดียวกันสำหรับแต่ละโมดูลที่มีส่วนหัว หากลิงค์เกอร์ไม่บ่นนั่นเป็นเพียงเพราะมันกัดลิ้นและสุภาพ

ในกรณีนี้เนื่องจากการconstที่staticจะบอกเป็นนัยและตัวเลือกด้วยเหตุนี้ ข้อสรุปคือไม่มีความอ่อนไหวต่อข้อผิดพลาดหลายนิยามตามที่ Mike F อ้าง
underscore_d

2

หนังสือ C (ออนไลน์ฟรี) มีบทเกี่ยวกับการเชื่อมโยงซึ่งอธิบายความหมายของ 'คงที่' โดยละเอียดยิ่งขึ้น (แม้ว่าจะมีคำตอบที่ถูกต้องอยู่แล้วในความคิดเห็นอื่น ๆ ): http://publications.gbdirect.co.uk/c_book /chapter4/linkage.html


2

เพื่อตอบคำถาม "คงหมายถึงสำเนาเพียงชุดเดียวของ VAL ถูกสร้างขึ้นในกรณีที่ส่วนหัวรวมอยู่ในไฟล์ต้นฉบับมากกว่าหนึ่งไฟล์" ...

NO VAL จะถูกกำหนดแยกกันเสมอในทุกไฟล์ที่มีส่วนหัว

มาตรฐานสำหรับ C และ C ++ ทำให้เกิดความแตกต่างในกรณีนี้

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

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

ใน C ++ ตัวแปร file-scoped จะเป็นแบบคงที่โดยค่าเริ่มต้นหากเป็น const และ extern ตามค่าเริ่มต้นหากไม่ใช่ หากคุณใช้ C ++ ทั้ง VAL และ ANOTHER_VAL จะคงที่

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

  • ตัวเลือกการดีบัก
  • ที่อยู่ในไฟล์
  • คอมไพเลอร์จัดสรรหน่วยเก็บข้อมูลเสมอ (ประเภท const ที่ซับซ้อนไม่สามารถอินไลน์ได้ง่ายดังนั้นจึงกลายเป็นกรณีพิเศษสำหรับประเภทพื้นฐาน)

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

1

สมมติว่าการประกาศเหล่านี้อยู่ในขอบเขตส่วนกลาง (เช่นไม่ใช่ตัวแปรสมาชิก) จากนั้น:

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

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


ฉันเชื่อว่าคอมไพเลอร์ต้องจัดสรรพื้นที่สำหรับ const int ในทุกกรณีเนื่องจากโมดูลอื่นสามารถพูดว่า "extern const int อะไรก็ได้บางอย่าง (& อะไรก็ตาม);"

1

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


"... แต่จะทำให้ซอร์สไฟล์แต่ละไฟล์ที่มีไฟล์ส่วนหัวมีสำเนาส่วนตัวของตัวแปรซึ่งอาจไม่ใช่สิ่งที่ตั้งใจไว้" - เนื่องจากความล้มเหลวของคำสั่งเริ่มต้นแบบคงที่อาจจำเป็นต้องมีสำเนาในแต่ละหน่วยการแปล
jww

1

ตัวแปรconstเป็นค่าเริ่มต้นคงที่ใน C ++ แต่ภายนอก C ดังนั้นหากคุณใช้ C ++ สิ่งนี้จะไม่รู้สึกว่าจะใช้โครงสร้าง

(7.11.6 C ++ 2003 และ Apexndix C มีตัวอย่าง)

ตัวอย่างเปรียบเทียบแหล่งคอมไพล์ / ลิงค์เป็นโปรแกรม C และ C ++:

bruziuz:~/test$ cat a.c
const int b = 22;
int main(){return 0;}
bruziuz:~/test$ cat b.c
const int b=2;
bruziuz:~/test$ gcc -x c -std=c89 a.c b.c
/tmp/ccSKKIRZ.o:(.rodata+0x0): multiple definition of `b'
/tmp/ccDSd0V3.o:(.rodata+0x0): first defined here
collect2: error: ld returned 1 exit status
bruziuz:~/test$ gcc -x c++ -std=c++03 a.c b.c 
bruziuz:~/test$ 
bruziuz:~/test$ gcc --version | head -n1
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.5) 5.4.0 20160609

มีเป็นstaticความรู้สึกในการยังรวมถึง มันส่งสัญญาณ / การรับรู้เจตนาของสิ่งที่โปรแกรมเมอร์ที่จะทำและรักษาความเท่าเทียมกันกับประเภทอื่น ๆ ของการประกาศ (และ FWIW, C) staticที่ขาดโดยปริยาย มันเหมือนกับการรวมvirtualและเมื่อเร็ว ๆ นี้overrideในการประกาศฟังก์ชันการลบล้าง - ไม่จำเป็น แต่มีการจัดทำเอกสารในตัวเองมากขึ้นและในกรณีหลังจะเอื้อต่อการวิเคราะห์แบบคงที่
underscore_d

ฉันเห็นด้วยอย่างไร้เหตุผล เช่นสำหรับฉันในชีวิตจริงฉันมักจะเขียนอย่างชัดเจน
bruziuz

"ดังนั้นถ้าคุณใช้ภาษา C ++ ความรู้สึกนี้สิ่งก่อสร้างเพื่อการใช้งาน ... ไม่" - อืม ... ฉันเพียงแค่รวบรวมโครงการที่ใช้เฉพาะในตัวแปรในส่วนหัวที่มีconst g++ (GCC) 7.2.1 20170915 (Red Hat 7.2.1-2)ส่งผลให้มีสัญลักษณ์ที่กำหนดแบบทวีคูณประมาณ 150 สัญลักษณ์ (หนึ่งตัวสำหรับแต่ละหน่วยการแปลที่มีส่วนหัวรวมอยู่ด้วย) ผมคิดว่าเราจำเป็นต้องอย่างใดอย่างหนึ่งstatic, inlineหรือที่ไม่ระบุชื่อ / ชื่อ namespace เพื่อหลีกเลี่ยงการเชื่อมโยงภายนอก
jww

ฉันลอง baby-example กับ gcc-5.4 พร้อมประกาศconst intภายในขอบเขตเนมสเปซและในเนมสเปซส่วนกลาง และรวบรวมและปฏิบัติตามกฎ "วัตถุที่ประกาศ const และไม่ได้ประกาศอย่างชัดเจนจากภายนอกว่ามีการเชื่อมโยงภายใน" ".... อาจจะด้วยเหตุผลบางประการในโครงการนี้ส่วนหัวนี้รวมอยู่ในแหล่งที่มาที่รวบรวม C ซึ่งกฎแตกต่างกันอย่างสิ้นเชิง
bruziuz

@jww ฉันอัปโหลดตัวอย่างที่มีปัญหาการเชื่อมโยงสำหรับ C และไม่มีปัญหาสำหรับ C ++
bruziuz

0

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

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


-2

Static ป้องกันไม่ให้คอมไพเลอร์เพิ่มอินสแตนซ์หลายรายการ สิ่งนี้มีความสำคัญน้อยลงด้วยการป้องกัน #ifndef แต่สมมติว่าส่วนหัวนั้นรวมอยู่ในไลบรารีสองไลบรารีแยกกันและแอปพลิเคชันเชื่อมโยงกันจะรวมอินสแตนซ์สองรายการ


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