“ คงที่ const” เทียบกับ“ #define” เทียบกับ“ enum”


585

ตัวเลือกใดดีกว่าที่จะใช้ในข้อความด้านล่างใน C

static const int var = 5;

หรือ

#define var 5

หรือ

enum { var = 5 };

35
น่าสนใจนี้เป็นเกือบตรงคำถามเดียวกับstackoverflow.com/questions/1637332/static-const-vs-define ข้อแตกต่างเพียงอย่างเดียวคือคำถามนั้นเกี่ยวกับ C ++ และอันนี้เป็นเรื่องเกี่ยวกับ C เนื่องจากคำตอบของฉันคือเฉพาะ C ++ ฉันบอกว่าทำให้พวกเขาไม่เหมือนกัน แต่คนอื่นอาจไม่เห็นด้วย
TED

53
ไม่เหมือนกันแน่นอน มีพื้นที่มากมายทั้งที่ C ++ อนุญาตให้ไวยากรณ์ C สำหรับเหตุผลด้านความเข้ากันได้ ในกรณีดังกล่าวคำถามเช่น "วิธีที่ดีที่สุดในการทำ X" จะมีคำตอบต่าง ๆ ใน C ++ เช่นการเริ่มต้นวัตถุ
MSalters

นอกจากนี้: stackoverflow.com/questions/1944041/…
jamesdlin

สิ่งนี้ไม่ได้เป็นพื้นฐานของความคิดเห็น? พวกเขาแต่ละคนมีจุดประสงค์ที่แตกต่างกัน
Sam Hammamy

1
@RobertSsupportsMonicaCellio ใช่ ขอบคุณสำหรับการแสดงความคิดเห็น
วีเจย์

คำตอบ:


690

ขึ้นอยู่กับสิ่งที่คุณต้องการสำหรับค่า คุณ (และคนอื่น ๆ ) ไม่ได้ใช้ตัวเลือกที่สาม:

  1. static const int var = 5;
  2. #define var 5
  3. enum { var = 5 };

ไม่สนใจปัญหาเกี่ยวกับการเลือกชื่อจากนั้น:

  • หากคุณต้องการส่งตัวชี้ไปรอบ ๆ คุณต้องใช้ (1)
  • เนื่องจาก (2) เป็นตัวเลือกคุณจึงไม่จำเป็นต้องส่งตัวชี้ไปรอบ ๆ
  • ทั้ง (1) และ (3) มีสัญลักษณ์ในตารางสัญลักษณ์ของดีบักเกอร์ซึ่งทำให้การดีบักง่ายขึ้น มีโอกาสมากที่ (2) จะไม่มีสัญลักษณ์ทำให้คุณสงสัยว่ามันคืออะไร
  • (1) ไม่สามารถใช้เป็นส่วนข้อมูลสำหรับอาร์เรย์ที่ขอบเขตส่วนกลาง ทั้ง (2) และ (3) สามารถ
  • (1) ไม่สามารถใช้เป็นมิติสำหรับอาร์เรย์แบบคงที่ที่ขอบเขตฟังก์ชัน ทั้ง (2) และ (3) สามารถ
  • ภายใต้ C99 ทั้งหมดเหล่านี้สามารถใช้สำหรับอาร์เรย์ในเครื่อง ในทางเทคนิคแล้วการใช้ (1) จะบ่งบอกถึงการใช้ VLA (อาเรย์ความยาวแปรผัน) แม้ว่าขนาดที่อ้างอิงโดย 'var' จะถูกกำหนดแน่นอนที่ขนาด 5
  • (1) ไม่สามารถใช้ในสถานที่เช่นคำสั่ง switch; ทั้ง (2) และ (3) สามารถ
  • (1) ไม่สามารถใช้เพื่อเริ่มต้นตัวแปรสแตติก ทั้ง (2) และ (3) สามารถ
  • (2) สามารถเปลี่ยนรหัสที่คุณไม่ต้องการเปลี่ยนแปลงได้เพราะมันถูกใช้โดยตัวประมวลผลล่วงหน้า; ทั้ง (1) และ (3) จะไม่มีผลข้างเคียงที่ไม่คาดคิดเช่นนั้น
  • คุณสามารถตรวจสอบได้ว่า (2) ตั้งไว้ในตัวประมวลผลล่วงหน้าหรือไม่ ทั้ง (1) และ (3) ไม่อนุญาตให้ทำเช่นนั้น

ดังนั้นในบริบทส่วนใหญ่ต้องการ 'enum' มากกว่าทางเลือก มิฉะนั้นสัญลักษณ์แสดงหัวข้อย่อยแรกและสุดท้ายมีแนวโน้มที่จะเป็นปัจจัยควบคุม - และคุณต้องคิดให้หนักขึ้นถ้าคุณต้องตอบสนองทั้งสองในครั้งเดียว

หากคุณถามเกี่ยวกับ C ++ คุณจะต้องใช้ตัวเลือก (1) - const แบบคงที่ - ทุกครั้ง


111
รายการที่ยอดเยี่ยม! ข้อเสียเปรียบอย่างหนึ่งenumคือพวกเขาใช้งานเป็นint([C99] 6.7.2.2/3) #defineช่วยให้คุณระบุไม่ได้ลงนามและระยะยาวด้วยUและLต่อท้ายและconstช่วยให้คุณให้ชนิด enumอาจทำให้เกิดปัญหากับการแปลงประเภทปกติ
Gauthier

37
(2) คนมักจะบ่นเกี่ยวกับความปลอดภัยของประเภท ฉันไม่เคยเข้าใจว่าทำไมไม่เพียงแค่ใช้ "#define var ((int) 5)" และไชโยคุณจะได้รับความปลอดภัยประเภทที่มีการกำหนด
Ingo Blackman

6
@ RedX: คุณจะต้องอยู่ในสภาพแวดล้อมที่แปลกประหลาดมากสำหรับพื้นที่ที่มีความกังวล ที่กล่าวว่าค่าenumมิได้#defineใช้พื้นที่พิเศษต่อ se ค่าจะปรากฏในรหัสวัตถุเป็นส่วนหนึ่งของคำแนะนำแทนที่จะเป็นหน่วยเก็บข้อมูลที่จัดสรรในส่วนข้อมูลหรือในกองหรือกองซ้อน คุณจะได้รับการจัดสรรพื้นที่สำหรับstatic const intคอมไพเลอร์ แต่คอมไพเลอร์อาจปรับให้เหมาะสมถ้าคุณไม่ใช้ที่อยู่
Jonathan Leffler

15
อีก 'การลงคะแนน' สำหรับenum(และstatic const): พวกเขาไม่สามารถเปลี่ยนแปลงได้ a defineสามารถเป็น#undefine'd โดยที่ a enumและstatic constถูกกำหนดให้เป็นค่าที่กำหนด
Daan Timmer

15
@QED: ไม่ขอบคุณ ค่าคงที่ง่าย ๆ ปลอดภัยนอกวงเล็บ หรือแสดงให้ฉันเห็นว่าโปรแกรมที่คาดว่าจะสามารถคอมไพล์ได้อย่างถูกต้องตามกฎหมายนั้นจะมีการเปลี่ยนแปลงโดยไม่มี 5 ในวงเล็บ หากเป็นอาร์กิวเมนต์ของแมโครสไตล์ฟังก์ชันหรือหากมีตัวดำเนินการใด ๆ ในนิพจน์คุณจะต้องโทษฉันถ้าฉันไม่รวมวงเล็บ แต่นั่นไม่ใช่กรณีที่นี่
Jonathan Leffler

282

พูด, พูดแบบทั่วไป, พูดทั่วๆไป:

static const

เพราะมันเคารพขอบเขตและเป็นประเภทที่ปลอดภัย

caveat เดียวที่ฉันเห็น: ถ้าคุณต้องการตัวแปรที่อาจกำหนดไว้ในบรรทัดคำสั่ง ยังมีทางเลือก:

#ifdef VAR // Very bad name, not long enough, too general, etc..
  static int const var = VAR;
#else
  static int const var = 5; // default value
#endif

เมื่อใดก็ตามที่เป็นไปได้แทนที่จะใช้มาโคร / จุดไข่ปลาให้ใช้ทางเลือกประเภทที่ปลอดภัย

หากคุณจำเป็นต้องใช้แมโครจริงๆ (ตัวอย่างเช่นคุณต้องการ__FILE__หรือ__LINE__) คุณควรตั้งชื่อแมโครของคุณให้ดีขึ้นด้วยความระมัดระวัง: ในแบบแผนการตั้งชื่อ Boost จะแนะนำตัวพิมพ์ใหญ่ทั้งหมดโดยเริ่มจากชื่อโครงการ (ที่นี่ BOOST_ ) ในขณะที่อ่านห้องสมุดคุณจะสังเกตเห็นว่านี่คือ (โดยทั่วไป) ตามด้วยชื่อของพื้นที่เฉพาะ (ไลบรารี) แล้วด้วยชื่อที่มีความหมาย

โดยทั่วไปแล้วจะทำให้ชื่อยาว :)


2
เห็นด้วย - ด้วย #define มีอันตรายทั่วไปของ mangling code เนื่องจากตัวประมวลผลล่วงหน้าไม่ได้รับรู้ถึงไวยากรณ์
NeilDurant

10
การใช้ #if ดีกว่า #ifdef แต่ฉันก็เห็นด้วย +1
Tim Post

58
นี่คือการประกาศเผยแพร่ C ++ มาตรฐาน คำตอบด้านล่างมีความชัดเจนมากในการอธิบายว่าตัวเลือกคืออะไรและมีความหมายอย่างไร โดยเฉพาะ: ฉันเพิ่งมีปัญหากับ "const const" มีคนใช้เพื่อกำหนด "ค่าคงที่" ประมาณ 2,000 รายการในไฟล์ส่วนหัว จากนั้นไฟล์ส่วนหัวนี้รวมอยู่ในไฟล์ประมาณ 100 ".c" และ ".cpp" => 8Mbytes สำหรับ "consts" ยิ่งใหญ่ ใช่ฉันรู้ว่าคุณอาจใช้ตัวเชื่อมโยงเพื่อลบ const ที่ไม่ได้อ้างอิง แต่ก็ยังทำให้คุณมี "consts" ที่อ้างอิง หมดพื้นที่มีอะไรผิดปกติกับคำตอบนี้
Ingo Blackman

2
@IngoBlackman: ด้วยคอมไพเลอร์ที่ดีมีเพียงที่staticที่มีที่อยู่เท่านั้นที่ควรอยู่ และหากมีการใช้ที่อยู่อย่างใดอย่างหนึ่งอาจไม่สามารถใช้#defineหรือenum(ไม่มีที่อยู่) ... ดังนั้นฉันจึงล้มเหลวที่จะเห็นว่ามีทางเลือกอื่นที่สามารถใช้งานได้ หากคุณสามารถทำได้ด้วย "การประเมินเวลารวบรวม" คุณอาจกำลังมองหาextern constแทน
Matthieu M.

15
@ Tim โพสต์: #ifอาจจะเป็นที่นิยมมากกว่า#ifdefธงบูล แต่ในกรณีนี้มันจะทำให้มันเป็นไปไม่ได้ที่จะกำหนดvarเป็น0จากบรรทัดคำสั่ง ดังนั้นในกรณีนี้#ifdefทำให้รู้สึกมากขึ้นตราบใดที่มีค่าทางกฎหมายสำหรับ0 var
Maarten

108

ใน C โดยเฉพาะ ใน C คำตอบที่ถูกต้องคือใช้#define(หรือถ้าเหมาะสมenum)

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

ดังนั้นใน C ตัวเลือกควรถูกกำหนดโดยวิธีที่คุณวางแผนที่จะใช้ค่าคงที่ของคุณ ตัวอย่างเช่นคุณไม่สามารถใช้const intวัตถุเป็นcaseป้ายกำกับ (ในขณะที่แมโครจะทำงาน) คุณไม่สามารถใช้const intวัตถุเป็นความกว้างบิตฟิลด์ (ในขณะที่แมโครจะทำงาน) ใน C89 / 90 คุณไม่สามารถใช้constวัตถุเพื่อระบุขนาดอาร์เรย์ (ในขณะที่แมโครทำงานได้) แม้แต่ใน C99 คุณไม่สามารถใช้constวัตถุเพื่อระบุขนาดของอาเรย์เมื่อคุณต้องการอาเรย์ที่ไม่ใช่VLA

หากนี่เป็นสิ่งสำคัญสำหรับคุณก็จะเป็นตัวเลือกของคุณ ส่วนใหญ่เวลาที่คุณจะไม่มีทางเลือก แต่กับการใช้งาน#defineใน C. และอย่าลืมอีกทางเลือกหนึ่งที่ก่อให้เกิดค่าคงที่จริงใน enumC.

ในconstวัตถุC ++ เป็นค่าคงที่ที่แท้จริงดังนั้นใน C ++ มันเกือบจะดีกว่าเสมอที่จะชอบconstชุดย่อย (ไม่จำเป็นต้องระบุอย่างชัดเจนstaticใน C ++)


6
"คุณไม่สามารถใช้วัตถุ int const เป็นป้ายกำกับกรณี (ในขณะที่แมโครจะทำงาน)" ---> เกี่ยวกับคำสั่งนี้ฉันได้ทดสอบตัวแปร int const ใน C ในกรณีที่สวิตช์เปลี่ยนมันทำงาน ....
john

8
@john: คุณต้องให้รหัสที่คุณทดสอบและตั้งชื่อคอมไพเลอร์เฉพาะ การใช้อconst intอบเจ็กต์ในตัวพิมพ์เล็กและตัวพิมพ์ใหญ่นั้นผิดกฎหมายในทุกภาษาของ C (แน่นอนว่าคอมไพเลอร์ของคุณมีอิสระที่จะสนับสนุนเป็นส่วนเสริมภาษา C ++ ที่ไม่ได้มาตรฐาน
AnT

11
"... และโดยทั่วไปแล้วจะไร้ประโยชน์ในกรณีที่ปฏิบัติได้จริง " ฉันไม่เห็นด้วย. มันมีประโยชน์อย่างสมบูรณ์ตราบใดที่คุณไม่จำเป็นต้องใช้ชื่อเป็นนิพจน์คงที่ คำว่า "คงที่" ใน C หมายถึงสิ่งที่สามารถประเมินได้ในเวลารวบรวม constหมายถึงอ่านอย่างเดียว const int r = rand();ถูกกฎหมายอย่างสมบูรณ์แบบ
Keith Thompson

ใน C ++ มันจะดีกว่าที่จะใช้constexprเมื่อเทียบกับการconstเป็นพิเศษกับstlภาชนะบรรจุเช่นหรือarray bitset
Mayukh Sarkar

1
@john คุณต้องทดสอบในswitch()แถลงการณ์ไม่ใช่caseอย่างใดอย่างหนึ่ง ฉันเพิ่งถูกจับในเรื่องนี้เช่นกัน☺
Hi-Angel

32

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

ถ้าเป็นอย่างนั้นเดนนิสริตชี่ จะเป็นคนที่ดีที่สุดคนเดียว ... ฮ่าฮ่าฮ่า ... :-)


6
+1 สำหรับการกล่าวถึงหน่วยความจำระบบฝังตัวบางตัวยังคงมีไม่มากนักแม้ว่าฉันอาจจะเริ่มจากการใช้ const แบบคงที่และเปลี่ยนเป็น #defines หากจำเป็นเท่านั้น
fluffyben

3
ฉันเพิ่งทดสอบมัน แท้จริงแล้ว const int ใช้หน่วยความจำเพิ่มเติมเปรียบเทียบกับ #define หรือ enum เนื่องจากเราเขียนโปรแกรมระบบฝังตัวเราจึงไม่สามารถจ่ายการใช้หน่วยความจำเพิ่มเติมได้ ดังนั้นเราจะกลับไปใช้ #define หรือ enum
Davide Andrea

2
จริง ๆ แล้วการพูดมันไม่จริง (อีกต่อไป) ว่า a constใช้หน่วยความจำ GCC (ทดสอบกับ 4.5.3 และรุ่นที่ใหม่กว่า) ปรับconst intให้เป็นตัวอักษรโดยตรงในรหัสของคุณเมื่อใช้ -O3 ดังนั้นหากคุณพัฒนา RAM ในระดับต่ำ (เช่น AVR) คุณสามารถใช้ C const ได้อย่างปลอดภัยหากคุณใช้ GCC หรือคอมไพเลอร์อื่นที่เข้ากันได้ ฉันไม่ได้ทดสอบ แต่คาดหวังว่า Clang จะทำสิ่งเดียวกัน btw
ราฟาเอล

19

ใน C #defineเป็นที่นิยมมากขึ้น คุณสามารถใช้ค่าเหล่านั้นในการประกาศขนาดอาร์เรย์ตัวอย่างเช่น:

#define MAXLEN 5

void foo(void) {
   int bar[MAXLEN];
}

ANSI C ไม่อนุญาตให้คุณใช้static consts ในบริบทนี้เท่าที่ฉันรู้ ใน C ++ คุณควรหลีกเลี่ยงมาโครในกรณีเหล่านี้ คุณสามารถเขียน

const int maxlen = 5;

void foo() {
   int bar[maxlen];
}

และออกไปstaticเพราะการเชื่อมโยงภายในถูกบอกเป็นนัยโดยconst[ใน C ++ เท่านั้น]


1
คุณหมายความว่าอย่างไรกับ "การเชื่อมโยงภายใน"? ฉันสามารถมีconst int MY_CONSTANT = 5;ไฟล์หนึ่งไฟล์และเข้าถึงด้วยไฟล์extern const int MY_CONSTANT;อื่นได้ ฉันไม่สามารถหาข้อมูลใด ๆ ในมาตรฐาน (อย่างน้อย C99) เกี่ยวกับconstการเปลี่ยนพฤติกรรมเริ่มต้น "6.2.2: 5 หากการประกาศตัวบ่งชี้สำหรับวัตถุที่มีขอบเขตและไม่มีข้อกำหนดระดับการจัดเก็บการเชื่อมโยงของมันเป็นภายนอก"
Gauthier

@Gauthier: ขออภัยเกี่ยวกับเรื่องนั้น ฉันควรจะพูดว่า "มีนัยโดย const อยู่แล้วในภาษา C ++" นี่คือเฉพาะสำหรับ C ++
sellibitze

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

1
ตั้งแต่ C99 ข้อมูลโค้ดที่สองของคุณถูกกฎหมาย barคือ VLA (อาร์เรย์ความยาวแปรผัน); คอมไพเลอร์มีแนวโน้มที่จะสร้างรหัสราวกับว่ามันมีความยาวคงที่
Keith Thompson

14

ข้อเสียเปรียบอีกข้อconstใน C คือคุณไม่สามารถใช้ค่าในการเริ่มต้นอื่นconstได้

static int const NUMBER_OF_FINGERS_PER_HAND = 5;
static int const NUMBER_OF_HANDS = 2;

// initializer element is not constant, this does not work.
static int const NUMBER_OF_FINGERS = NUMBER_OF_FINGERS_PER_HAND 
                                     * NUMBER_OF_HANDS;

แม้สิ่งนี้จะไม่ทำงานกับ const เนื่องจากคอมไพเลอร์ไม่เห็นว่าเป็นค่าคงที่:

static uint8_t const ARRAY_SIZE = 16;
static int8_t const lookup_table[ARRAY_SIZE] = {
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; // ARRAY_SIZE not a constant!

ฉันยินดีที่จะใช้พิมพ์constในกรณีเหล่านี้มิฉะนั้น ...


5
สายไปนิดหน่อยถึงเกม แต่คำถามนี้เกิดขึ้นในอีกคำถามหนึ่ง การไล่ล่าว่าทำไมstatic uint8_t const ARRAY_SIZE = 16;ทันใดนั้นการคอมไพล์ของคุณทั้งหมดจึงไม่ใช่เรื่องยากอีกต่อไปโดยเฉพาะอย่างยิ่งเมื่อ#define ARRAY_SIZE 256มีการฝังสิบชั้นลึกลงไปในส่วนหัวที่พันกัน ชื่อตัวพิมพ์ใหญ่ทั้งหมดARRAY_SIZEกำลังถามถึงปัญหา จอง ALL_CAPS สำหรับมาโครและห้ามกำหนดมาโครที่ไม่ได้อยู่ในรูปแบบ ALL_CAPS
David Hammen

@ David: คำแนะนำเสียงที่ฉันจะทำตาม
Gauthier

1
4 ปีต่อมาคุณช่วยฉันประหยัดเวลาได้มากในการหาสาเหตุที่ฉันไม่สามารถ "ทำรัง" constได้ นี่สามารถอัปโหลดได้มากขึ้น!
Plouff

11

หากคุณสามารถหนีไปstatic constได้มีข้อดีมากมาย มันเป็นไปตามหลักการของขอบเขตปกติสามารถมองเห็นได้ในตัวดีบั๊กและโดยทั่วไปแล้วจะปฏิบัติตามกฎที่ตัวแปรเชื่อฟัง

อย่างไรก็ตามอย่างน้อยในมาตรฐาน C ดั้งเดิมมันคงไม่เป็นจริง หากคุณใช้#define var 5คุณสามารถเขียนint foo[var];เป็นการประกาศ แต่คุณไม่สามารถทำเช่นนั้นได้ (ยกเว้นเป็นส่วนขยายคอมไพเลอร์ "ด้วยstatic const int var = 5;นี่ไม่ใช่กรณีใน C ++ ที่ซึ่งstatic constรุ่นนี้สามารถใช้งานได้ทุกที่ที่เป็น#defineไปได้และฉันเชื่อว่า เป็นกรณีที่มี C99

อย่างไรก็ตามอย่าตั้งชื่อ#defineค่าคงที่ด้วยชื่อตัวพิมพ์เล็ก มันจะแทนที่การใช้ชื่อที่เป็นไปได้ใด ๆ จนกว่าจะสิ้นสุดของหน่วยการแปล ค่าคงที่มาโครควรอยู่ในเนมสเปซของตัวเองอย่างมีประสิทธิภาพซึ่งเป็นตัวพิมพ์ใหญ่ทั้งหมดซึ่งอาจเป็นส่วนนำหน้า


6
น่าเสียดายที่นี่ไม่ใช่กรณีของ C99 constใน C99 ยังคงไม่คงที่จริง คุณสามารถประกาศขนาดอาร์เรย์ด้วย a constใน C99 แต่เนื่องจาก C99 รองรับ Array Length Variable ด้วยเหตุผลนี้มันจะทำงานเฉพาะที่อนุญาต VLA ยกตัวอย่างเช่นแม้ใน C99, คุณยังไม่สามารถใช้กับขนาดของการประกาศอาร์เรย์สมาชิกในconst struct
AnT

ในขณะที่ถูกต้องว่า C99 จะไม่ยอมให้คุณทำเช่นนั้น GCC (ทดสอบกับ 4.5.3) จะช่วยให้คุณสามารถเริ่มต้นอาร์เรย์ด้วยconst intขนาดได้อย่างสมบูรณ์ราวกับว่าเป็น C ++ const หรือมาโคร ไม่ว่าคุณต้องการขึ้นอยู่กับการเบี่ยงเบนของ GCC จากมาตรฐานนี้เป็นทางเลือกของคุณเองฉันจะไปกับมันด้วยตัวคุณเองเว้นแต่คุณจะมองข้ามโดยใช้คอมไพเลอร์อื่นกว่า GCC หรือ Clang จริง ๆ มีคุณสมบัติเดียวกันที่นี่ 3.7)
Raphael

7

ควรใช้ const แทน #define เสมอ นั่นเป็นเพราะ const ได้รับการดูแลโดยคอมไพเลอร์และ #define โดย preprocessor มันเหมือน #define ตัวเองไม่ได้เป็นส่วนหนึ่งของรหัส (พูดคร่าว ๆ )

ตัวอย่าง:

#define PI 3.1416

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

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

สารละลาย:

const double PI = 3.1416; //or static const...

6

#define var 5mystruct.varจะทำให้คุณเดือดร้อนถ้าคุณมีสิ่งที่ต้องการ

ตัวอย่างเช่น,

struct mystruct {
    int var;
};

#define var 5

int main() {
    struct mystruct foo;
    foo.var = 1;
    return 0;
}

ตัวประมวลผลล่วงหน้าจะแทนที่และรหัสจะไม่คอมไพล์ ด้วยเหตุนี้สไตล์การเขียนโปรแกรมดั้งเดิมแนะนำให้ค่าคงที่ทั้งหมด#defineใช้อักษรตัวใหญ่เพื่อหลีกเลี่ยงความขัดแย้ง


6

ฉันเขียนโปรแกรมทดสอบอย่างรวดเร็วเพื่อแสดงความแตกต่าง:

#include <stdio.h>

enum {ENUM_DEFINED=16};
enum {ENUM_DEFINED=32};

#define DEFINED_DEFINED 16
#define DEFINED_DEFINED 32

int main(int argc, char *argv[]) {

   printf("%d, %d\n", DEFINED_DEFINED, ENUM_DEFINED);

   return(0);
}

สิ่งนี้รวบรวมด้วยข้อผิดพลาดและคำเตือนเหล่านี้:

main.c:6:7: error: redefinition of enumerator 'ENUM_DEFINED'
enum {ENUM_DEFINED=32};
      ^
main.c:5:7: note: previous definition is here
enum {ENUM_DEFINED=16};
      ^
main.c:9:9: warning: 'DEFINED_DEFINED' macro redefined [-Wmacro-redefined]
#define DEFINED_DEFINED 32
        ^
main.c:8:9: note: previous definition is here
#define DEFINED_DEFINED 16
        ^

โปรดทราบว่า enum ให้ข้อผิดพลาดเมื่อ define ให้คำเตือน


4

คำนิยาม

const int const_value = 5;

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

const int const_value = 5;
int *mutable_value = (int*) &const_value;
*mutable_value = 3;
printf("%i", const_value); // The output may be 5 or 3, depending on the compiler.

นี่หมายถึงคำจำกัดความ

#define CONST_VALUE 5

เป็นวิธีเดียวที่จะกำหนดค่าคงที่ซึ่งไม่สามารถแก้ไขได้ด้วยวิธีการใด ๆ


8
การปรับเปลี่ยนค่าคงที่โดยใช้ตัวชี้เป็นพฤติกรรมที่ไม่ได้กำหนด หากคุณเต็มใจที่จะไปที่นั่น#defineสามารถแก้ไขได้โดยแก้ไขรหัสเครื่อง
ugoren

คุณเป็นส่วนหนึ่งที่ถูกต้อง ฉันทดสอบโค้ดด้วย Visual Studio 2012 และพิมพ์5ออกมา แต่ไม่มีใครแก้ไขได้#defineเพราะเป็นมาโครตัวประมวลผลล่วงหน้า ไม่มีอยู่ในโปรแกรมไบนารี หากต้องการแก้ไขสถานที่ทั้งหมดที่CONST_VALUEใช้สถานที่แห่งนั้นต้องทำทีละสถานที่
user2229691

3
@ugoren: สมมติว่าคุณเขียน#define CONST 5แล้วif (CONST == 5) { do_this(); } else { do_that(); }และคอมไพเลอร์กำจัดelseสาขา คุณเสนอให้แก้ไขรหัสเครื่องเพื่อเปลี่ยนCONSTเป็น 6 อย่างไร
Keith Thompson

@ KeithThompson ฉันไม่เคยบอกว่ามันสามารถทำได้อย่างง่ายดายและเชื่อถือได้ เพียงแค่นั้น#defineไม่ได้พิสูจน์กระสุน
ugoren

3
@ugoren: จุดของฉันนั่นคือ "การแก้ไขโค้ดเครื่อง" #defineไม่ได้เป็นวิธีที่เหมาะสมที่จะทำซ้ำผลของการเปลี่ยนแปลงค่าของที่ วิธีเดียวที่ทำได้คือแก้ไขซอร์สโค้ดและคอมไพล์ใหม่
Keith Thompson

4

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

สำหรับจำนวนเต็มถ้าคุณอยู่ในสภาพแวดล้อมแบบฝังตัวที่มีหน่วยความจำที่ จำกัด มากคุณอาจต้องกังวลเกี่ยวกับที่เก็บค่าคงที่และวิธีการเข้าถึงคอมไพล์ คอมไพเลอร์อาจเพิ่มสอง consts ในเวลาทำงาน แต่เพิ่ม #defines สองครั้งในเวลารวบรวม #define คงที่อาจถูกแปลงเป็นหนึ่งหรือมากกว่าหนึ่ง MOV [ทันที] คำแนะนำซึ่งหมายความว่าค่าคงที่จะถูกเก็บไว้อย่างมีประสิทธิภาพในหน่วยความจำโปรแกรม ค่าคงที่ const จะถูกเก็บไว้ในส่วน. const ในหน่วยความจำข้อมูล ในระบบที่มีสถาปัตยกรรมของ Harvard อาจมีความแตกต่างในด้านประสิทธิภาพและการใช้หน่วยความจำแม้ว่าจะมีขนาดเล็กก็ตาม พวกเขาอาจมีความสำคัญสำหรับการเพิ่มประสิทธิภาพฮาร์ดคอร์ของวงใน


3

อย่าคิดว่าจะมีคำตอบสำหรับ "ซึ่งดีที่สุดเสมอ" แต่อย่างที่ Matthieu กล่าว

static const

เป็นประเภทที่ปลอดภัย #defineแม้ว่าสัตว์เลี้ยงที่ใหญ่ที่สุดของฉันด้วยคือเมื่อการดีบักในVisual Studioคุณไม่สามารถดูตัวแปร มันทำให้เกิดข้อผิดพลาดที่ไม่สามารถหาสัญลักษณ์ได้


1
"คุณไม่สามารถดูตัวแปร" ใช่ไม่ใช่ตัวแปร ไม่เปลี่ยนทำไมคุณต้องดูมัน คุณสามารถค้นหาได้ทุกที่ที่ใช้เพียงแค่ค้นหาป้ายกำกับ ทำไมคุณต้อง (หรือต้องการ) เพื่อดู #define
Marshall Eubanks

3

อีกทางเลือกหนึ่ง#defineซึ่งให้การกำหนดขอบเขตที่เหมาะสม แต่มีพฤติกรรมเหมือนค่าคงที่ "ของจริง" คือ "enum" ตัวอย่างเช่น:

enum {number_ten = 10;}

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

ข้อแม้ที่สำคัญอย่างหนึ่งในการทำเช่นนั้น: ใน C ++ ประเภทที่แจกแจงมีความเข้ากันได้ที่ จำกัด กับจำนวนเต็ม ตัวอย่างเช่นโดยค่าเริ่มต้นเราไม่สามารถดำเนินการทางคณิตศาสตร์กับพวกเขา ฉันพบว่าเป็นพฤติกรรมเริ่มต้นที่แปลกประหลาดสำหรับ enums; ในขณะที่มันคงจะดีถ้ามีประเภท "เข้มงวด enum" เนื่องจากความปรารถนาที่จะมี C ++ โดยทั่วไปเข้ากันได้กับ C ฉันคิดว่าพฤติกรรมเริ่มต้นของประเภท "enum" ควรใช้แทนกันได้กับจำนวนเต็ม


1
ใน C ค่าคงที่การแจงนับจะเป็นประเภทเสมอintดังนั้นจึงไม่สามารถใช้ "enum hack" กับประเภทจำนวนเต็มอื่น ๆ ได้ ( ประเภทการแจงนับเข้ากันได้กับชนิดจำนวนเต็มที่กำหนดใช้งานบางอย่างไม่จำเป็นintแต่ในกรณีนี้ชนิดไม่ระบุชื่อดังนั้นไม่สำคัญ)
Keith Thompson

@ KeithThompson: ตั้งแต่ที่ฉันเขียนข้างต้นฉันได้อ่านว่า MISRA-C จะเหยียดหากคอมไพเลอร์กำหนดประเภทอื่นที่ไม่ใช่intตัวแปรการแจงนับ (ซึ่งคอมไพเลอร์ได้รับอนุญาตให้ทำ) และพยายามพยายามกำหนดให้ตัวแปรเช่นนั้น สมาชิกของการแจงนับของตัวเอง ฉันหวังว่าคณะกรรมการมาตรฐานจะเพิ่มวิธีการประกาศประเภทจำนวนเต็มแบบพกพาด้วยความหมายที่ระบุ แพลตฟอร์มใด ๆโดยไม่คำนึงcharถึงขนาดควรจะสามารถประกาศประเภทที่ห่อหุ้ม mod 65536 แม้ว่าคอมไพเลอร์จะต้องเพิ่มAND R0,#0xFFFFคำสั่งจำนวนมากหรือเทียบเท่า
supercat

คุณสามารถใช้งานuint16_tได้ แต่แน่นอนว่าไม่ใช่ประเภทการแจงนับ มันเป็นการดีที่จะให้ผู้ใช้ระบุชนิดจำนวนเต็มที่ใช้เพื่อแสดงชนิดการแจงนับที่กำหนด แต่คุณสามารถบรรลุเอฟเฟ็กต์แบบเดียวกันได้ด้วยการใช้typedeffor uint16_tและ a series #defineสำหรับแต่ละค่า
Keith Thompson

1
@ KeithThompson: ฉันเข้าใจว่าด้วยเหตุผลทางประวัติศาสตร์เรายึดติดกับความจริงที่ว่าบางแพลตฟอร์มจะประเมิน2U < -1Lว่าเป็นจริงและอื่น ๆ ว่าเป็นเท็จและตอนนี้เราติดอยู่กับข้อเท็จจริงที่ว่าแพลตฟอร์มบางแห่งจะใช้การเปรียบเทียบระหว่างuint32_tและint32_tลงชื่อ และบางคนไม่ได้ลงนาม แต่นั่นไม่ได้หมายความว่าคณะกรรมการไม่สามารถกำหนดผู้สืบทอดที่เข้ากันได้กับ C ที่มีประเภทซึ่งความหมายจะสอดคล้องกับคอมไพเลอร์ทั้งหมด
supercat

1

ความแตกต่างง่ายๆ:

ณ เวลาประมวลผลล่วงหน้าค่าคงที่จะถูกแทนที่ด้วยค่าของมัน ดังนั้นคุณไม่สามารถใช้โอเปอเรเตอร์ dereference กับ define แต่คุณสามารถใช้โอเปอเรเตอร์ dereference กับตัวแปรได้

อย่างที่คุณคิดสมมุติว่าเร็วขึ้นคงที่ const

ตัวอย่างเช่น:

#define mymax 100

printf("address of constant is %p",&mymax);คุณไม่สามารถทำ

แต่มี

const int mymax_var=100

printf("address of constant is %p",&mymax_var);ที่คุณสามารถทำได้

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

อย่างไรก็ตามสำหรับ const คงที่เรามีตัวแปรที่ได้รับการจัดสรรที่ไหนสักแห่ง สำหรับ gcc const แบบคงที่จะถูกจัดสรรในส่วนข้อความของโปรแกรม

ด้านบนฉันต้องการบอกเกี่ยวกับผู้ดำเนินการอ้างอิงดังนั้นแทนที่การอ้างอิงด้วยการอ้างอิง


1
คำตอบของคุณผิดมาก นี่คือประมาณ C คำตอบของคุณเกี่ยวข้องกับ C ++ ซึ่งมีความหมายแตกต่างกันมากสำหรับผู้constคัดเลือก C ไม่ได้มีค่าคงที่ symbolica อื่น ๆ กว่าenum-ค่าคงที่ A const intคือตัวแปร คุณสับสนกับภาษาและการใช้งานเฉพาะ ไม่มีความต้องการที่จะวางวัตถุ และมันไม่เป็นความจริงแม้แต่น้อยสำหรับ gcc: โดยทั่วไปแล้วจะวางconstตัวแปรที่ผ่านการรับรองใน.rodataส่วน แต่นั่นก็ขึ้นอยู่กับแพลตฟอร์มเป้าหมาย &และคุณหมายถึงที่อยู่ของผู้ประกอบการ
ซื่อสัตย์เกินไปสำหรับไซต์นี้

0

เราดูรหัสแอสเซมเบลอร์ที่ผลิตบน MBF16X ... ตัวแปรทั้งสองส่งผลให้มีรหัสเดียวกันสำหรับการดำเนินการทางคณิตศาสตร์ (เช่นเพิ่มทันที)

ดังนั้นจึงconst intเป็นที่ต้องการสำหรับการตรวจสอบประเภทในขณะที่#defineเป็นแบบเก่า อาจเป็นเฉพาะคอมไพเลอร์ ดังนั้นตรวจสอบรหัสแอสเซมเบลอร์ของคุณ


-1

ฉันไม่แน่ใจว่าถ้าฉันถูก แต่ในความเห็นของฉันเรียก#defineค่า d เร็วกว่าการเรียกตัวแปรอื่น ๆ ที่ประกาศตามปกติ (หรือค่า const) เป็นเพราะเมื่อโปรแกรมทำงานและจำเป็นต้องใช้ตัวแปรที่ประกาศตามปกติบางอย่างมันต้องข้ามไปยังตำแหน่งที่แน่นอนในหน่วยความจำเพื่อรับตัวแปรนั้น

ในทางตรงกันข้ามเมื่อใช้#defineค่า d โปรแกรมไม่จำเป็นต้องข้ามไปยังหน่วยความจำที่จัดสรรใด ๆ เพียงใช้ค่า ถ้า#define myValue 7และเรียกโปรแกรมก็จะทำงานตรงเช่นเดียวกับเมื่อมันเป็นเพียงแค่การโทรmyValue7

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