เหตุใดขนาดของ (x ++) จึงไม่เพิ่มขึ้น x


505

นี่คือโค้ดที่คอมไพล์ใน windows dev c ++:

#include <stdio.h>

int main() {
    int x = 5;
    printf("%d and ", sizeof(x++)); // note 1
    printf("%d\n", x); // note 2
    return 0;
}

ผมคาดว่าxจะเป็น 6 หลังจากรัน1 ข้อความ อย่างไรก็ตามผลลัพธ์คือ:

4 and 5

ทุกคนสามารถอธิบายได้ว่าทำไมxไม่ได้เพิ่มขึ้นหลังจากที่ทราบ 1 ?


37
ฉันทราบว่า DevC ++ ใช้คอมไพเลอร์ล้าสมัยที่เก่ามากคุณอาจต้องการอัพเกรดเป็น IDE รุ่นใหม่เช่น Codeblocks Eclipse หรือ Visual Studio
Tom J Nowell

4
++ x ให้ผลลัพธ์เช่นเดียวกับ x ++, cygwin และ gcc 3.4.4
yehnan

6
@Tim Pletzcker เนื่องจากค่าแรกคือขนาดของตัวแปรและไม่ใช่ตัวแปร
Surfbutler

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

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

คำตอบ:


537

จากมาตรฐาน C99 (ความสำคัญคือของฉัน)

6.5.3.4/2

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


51
"ถ้าชนิดของตัวถูกดำเนินการเป็นชนิดอาร์เรย์ความยาวผันแปรตัวถูกดำเนินการจะถูกประเมิน" wow! ฉันไม่เคยรู้เลยว่า
Kos

6
คุณหมายถึงอะไรโดยใช้อาเรย์แบบตัวแปรความยาว? นั่นหมายความว่าตัวถูกดำเนินการเป็นอาร์เรย์หรือไม่ รหัสในกรณีนี้ไม่ใช่อาร์เรย์ คุณช่วยเคลียร์สิ่งต่าง ๆ ให้ฉันได้ไหม?
Neigyl R. Noval

37
แถวยาวตัวแปรอาร์เรย์ประกาศด้วยขนาดที่ไม่รู้จักเป็นค่าในระหว่างการรวบรวมตัวอย่างเช่นถ้าคุณอ่านNจาก stdin int array[N]และแต่งหน้า นี่เป็นหนึ่งในคุณสมบัติ C99 ซึ่งไม่สามารถใช้งานได้ใน C ++
Kos

21
@ LegendofCage โดยเฉพาะสิ่งนี้จะหมายถึงว่าในบางสิ่งเช่นsizeof(int[++x])(จริง ๆ แล้วเป็นความคิดที่ไม่ดี แต่อย่างใด) ++สามารถประเมินได้
Jens Gustedt

3
@ Joe Wreschnig: มันคือการประเมินโดยgcc, clangและในideone.com/Pf7iF
JFS

190

sizeofเป็นตัวดำเนินการคอมไพล์เวลาดังนั้น ณ เวลาของการรวบรวมsizeofและตัวถูกดำเนินการถูกแทนที่ด้วยค่าผลลัพธ์ ถูกดำเนินการจะไม่ได้รับการประเมิน (ยกเว้นเมื่อมันเป็นความยาวอาร์เรย์ตัวแปร) ที่ทุก เฉพาะประเภทของเรื่องผล

short func(short x) {  // this function never gets called !!
   printf("%d", x);    // this print never happens
   return x;
}

int main() {
   printf("%d", sizeof(func(3))); // all that matters to sizeof is the 
                                  // return type of the function.
   return 0;
}

เอาท์พุท:

2

เป็นshortหมกมุ่นอยู่ 2 ไบต์ในเครื่องของฉัน

การเปลี่ยนประเภทที่ส่งคืนของฟังก์ชันเป็นdouble:

double func(short x) {
// rest all same

จะให้8เป็นเอาท์พุท


12
เฉพาะบางครั้ง - มันเป็นเวลารวบรวมถ้าเป็นไปได้
Martin Beckett

10
-1 เนื่องจากสิ่งนี้ขัดแย้งกับคำตอบที่ถูกต้อง (และถูกต้อง) และไม่ได้อ้างอิงมาตรฐาน
sam hocevar

1
มีประโยชน์อย่างมากในการรวบรวมเวลาของตัวดำเนินการ sizeof () เมื่อทำงานกับสตริง หากคุณมีสตริงที่กำหนดค่าเริ่มต้นเป็นสตริงที่ยกมาแทนการใช้ strlen () โดยที่อาร์เรย์อักขระที่ประกอบไปด้วยสตริงจะต้องถูกสแกนสำหรับ null-terminator ณ รันไทม์ sizeof (quote_string) เป็นที่ทราบกัน ณ เวลารวบรวม และดังนั้นในเวลาทำงาน มันเป็นเรื่องเล็กน้อย แต่ถ้าคุณใช้สตริงที่ยกมาในวงวนล้าน ๆ ครั้งมันจะสร้างความแตกต่างอย่างมากในประสิทธิภาพ
user2548100

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

47

sizeof(foo) พยายามอย่างหนักเพื่อค้นหาขนาดของนิพจน์ในเวลารวบรวม:

6.5.3.4:

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

ในระยะสั้น: อาร์เรย์ความยาวตัวแปรให้เรียกใช้ที่รันไทม์ (หมายเหตุ: Variable Length Araysเป็นคุณสมบัติเฉพาะ - ไม่ใช่อาร์เรย์ที่จัดสรรด้วยmalloc(3)) มิฉะนั้นเฉพาะประเภทของนิพจน์ที่คำนวณและในเวลารวบรวม


33

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

(sizeof x)  //this also works

1
แต่นี่เป็นคำตอบของคำถามได้อย่างไร
เซบาสเตียนมัค

5
@phresnel: นี่เป็นเพียงเพื่อให้ชัดเจนว่า sizeof เป็น "แปลก" และไม่อยู่ภายใต้กฎของฟังก์ชั่นปกติ ฉันแก้ไขโพสต์ต่อไปเพื่อลบความสับสนที่อาจเกิดขึ้นกับตัวดำเนินการรันไทม์ปกติเช่น (+) และ (-)
hugomg

sizeofผู้ประกอบการจะไม่ได้เป็นผู้ประกอบการรวบรวมเวลาคุณจะต้องให้ VLA ที่จะคิดออกนี้
paxdiablo

21

บันทึก

คำตอบนี้ถูกรวมเข้าด้วยกันจากคำซ้ำซึ่งอธิบายถึงวันที่ล่าช้า

เป็นต้นฉบับ

ยกเว้นอาร์เรย์ยาวตัวแปร sizeofไม่ได้ประเมินอาร์กิวเมนต์ เราสามารถดูได้จากส่วนมาตรฐาน C99 ฉบับร่างขนาด6.5.3.4 ตัวดำเนินการวรรค2ซึ่งระบุว่า:

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

ความคิดเห็น ( ตอนนี้ถูกลบ ) ถามว่าสิ่งนี้จะประเมินในเวลาทำงาน:

sizeof( char[x++]  ) ;

และแน่นอนว่ามันจะเป็นเช่นนี้เช่นกัน ( เห็นพวกเขาทั้งคู่มีชีวิตอยู่ ):

sizeof( char[func()]  ) ;

เนื่องจากทั้งสองเป็นอาร์เรย์ความยาวผันแปร แม้ว่าฉันจะไม่เห็นการใช้งานจริงมากนัก

หมายเหตุ: อาร์เรย์ความยาวผันแปรได้รับการกล่าวถึงในส่วนมาตรฐาน C99 ฉบับร่างส่วนผู้6.7.5.2 ประกาศอาร์เรย์วรรค 4 :

[... ] ถ้าขนาดเป็นนิพจน์ค่าคงที่จำนวนเต็มและชนิดองค์ประกอบมีขนาดค่าคงที่ที่รู้จักชนิดอาร์เรย์จะไม่เป็นชนิดอาร์เรย์ความยาวแปรผัน ไม่เช่นนั้นประเภทอาเรย์จะเป็นประเภทอาเรย์ที่มีความยาวผันแปรได้

ปรับปรุง

ใน C11 คำตอบจะเปลี่ยนไปสำหรับเคส VLA ในบางกรณีจะไม่มีการระบุว่าจะประเมินขนาดนิพจน์หรือไม่ จากส่วนผู้6.7.6.2 ประกาศอาร์เรย์ซึ่งพูดว่า:

[... ] ที่นิพจน์ขนาดเป็นส่วนหนึ่งของตัวถูกดำเนินการของตัวดำเนินการ sizeof และการเปลี่ยนค่าของขนาดนิพจน์จะไม่ส่งผลกระทบต่อผลลัพธ์ของตัวดำเนินการ

ตัวอย่างเช่นในกรณีเช่นนี้ ( ดูสด ):

sizeof( int (*)[x++] )

1
สิ่งสำคัญที่ควรจำที่นี่คือเกือบตลอดเวลา sizeofเป็นแมโครอย่างมีประสิทธิภาพ - มันไม่ได้สร้างโค้ด แต่จะคำนวณค่าล่วงหน้าที่คาดไว้และฝากไว้ในโค้ดโดยตรง โปรดทราบว่านี่เป็นพฤติกรรมเดียวจนกระทั่ง C99 เนื่องจาก VBA ไม่มีอยู่จริง (ฉันไม่เคยได้ยินพวกเขาจนกระทั่งคำตอบนี้เชื่อหรือไม่!)
Corley Brigman

ในทางใดจะsizeof (char[x++]);ใช้ค่าของxสิ่งอื่นนอกเหนือจากการกำหนดค่าของการแสดงออกx++และค่าใหม่สำหรับxซึ่งทั้งสองเป็นเรื่องปกติกับตัวดำเนินการนั้น
supercat

@alk ... เอ่อใช่ฉันหมายถึง 'VLA' แน่นอน :) Shafik - ทำไมพวกนั้นถึงประเมินตอนรันไทม์? อย่างที่ฉันบอกว่าฉันไม่เคยเห็น VLA มาก่อน แต่ประเภทของทั้งคู่เป็นที่รู้จักกันในเวลารวบรวมใช่มั้ย
Corley Brigman

@CorleyBrigman คำตอบเร็ว ๆ นั้นน่าจะเป็น b / c ที่มาตรฐานบอกเช่นนั้น แต่เหตุผลคือ b / c เราไม่ทราบขนาดของอาเรย์ในเวลาคอมไพล์ดังนั้นเราต้องประเมินนิพจน์ในเวลาทำงาน Vlas เป็นหัวข้อที่น่าสนใจที่นี่มีสองโพสต์ที่ฉันมีกับพวกเขาที่นี่และที่นี่
Shafik Yaghmour

@Shafik - อ่าโอเคฉันคิดว่าฉันสับสนเพราะฉันไม่รู้ว่าchar[x++]เป็น VLA มันดูเหมือนchar*กับดวงตาที่ไม่ได้ทาสีของฉันอย่างมีประสิทธิภาพ
Corley Brigman

11

เนื่องจากตัวดำเนินการของsizeofตัวดำเนินการไม่ได้รับการประเมินคุณสามารถทำได้:

int f(); //no definition, which means we cannot call it

int main(void) {
        printf("%d", sizeof(f()) );  //no linker error
        return 0;
}

การสาธิตออนไลน์: http://ideone.com/S8e2Y

นั่นคือคุณไม่จำเป็นต้องกำหนดฟังก์ชั่นfถ้ามันถูกใช้ในsizeofเท่านั้น เทคนิคนี้ส่วนใหญ่ใช้ใน C ++ เทมเพลต metaprogramming เช่นเดียวกับใน C ++ ตัวถูกดำเนินการของsizeofไม่ได้รับการประเมิน

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

ใน C ++ คุณสามารถทำได้:

struct A
{
  A(); //no definition, which means we cannot create instance!
  int f(); //no definition, which means we cannot call it
};

int main() {
        std::cout << sizeof(A().f())<< std::endl;
        return 0;
}

แต่ดูเหมือนว่าในsizeofครั้งแรกที่ฉันสร้างอินสแตนซ์AโดยการเขียนA()แล้วเรียกใช้ฟังก์ชันfบนอินสแตนซ์โดยการเขียนA().f()แต่ไม่มีสิ่งใดเกิดขึ้น

ตัวอย่าง: http://ideone.com/egPMi

นี่คือหัวข้อที่อธิบายคุณสมบัติที่น่าสนใจอื่น ๆ ของsizeof:


10

การดำเนินการไม่สามารถเกิดขึ้นได้ในระหว่างการรวบรวม ดังนั้น++i/ i++จะไม่เกิดขึ้น นอกจากนี้sizeof(foo())จะไม่ดำเนินการฟังก์ชั่น แต่กลับประเภทที่ถูกต้อง


2
" การดำเนินการไม่สามารถเกิดขึ้นได้ในระหว่างการรวบรวม " คุณหมายถึงอะไร
curiousguy

1
การรวบรวมจะสร้างรหัสวัตถุเท่านั้น ... รหัสวัตถุจะถูกดำเนินการเฉพาะเมื่อผู้ใช้ดำเนินการไบนารี เมื่อขนาดของการคอมไพล์เกิดขึ้นในเวลาที่สมมติว่า i ++ การเพิ่มขึ้นจะผิด
rakesh

" เมื่อขนาดของเกิดขึ้น ณ เวลารวบรวม " คุณหมายถึง: "เช่นเดียวกับsizeofนิพจน์ค่าคงที่เวลารวบรวม" หรือไม่
curiousguy

เช่นเดียวกับ "#define" เกิดขึ้นระหว่างการประมวลผลล่วงหน้าขนาดเดียวกันจะเกิดขึ้นในเวลารวบรวม ในระหว่างการรวบรวมข้อมูลประเภททั้งหมดจะมีให้เพื่อประเมินขนาดของมันและในระหว่างการรวบรวมและค่าจะถูกแทนที่ ดังที่ได้กล่าวแล้วโดย @pmg "จากมาตรฐาน C99" มาก่อน
rakesh

1
" sizeof จะเกิดขึ้นที่รวบรวมเวลา " สำหรับบางสิ่งบางอย่างที่ไม่ได้เป็นอาร์เรย์ตัวแปรความยาว
curiousguy

0

sizeofรันที่คอมไพล์เวลา แต่x++สามารถประเมินได้ในเวลาทำงานเท่านั้น ในการแก้ปัญหานี้มาตรฐาน C ++ กำหนดว่าตัวถูกดำเนินการsizeofไม่ได้รับการประเมิน The C Standard พูดว่า:

หากชนิดของตัวถูกดำเนินการ [ของsizeof] เป็นชนิดอาร์เรย์ความยาวผันแปรตัวถูกดำเนินการจะถูกประเมิน มิฉะนั้นตัวถูกดำเนินการไม่ได้รับการประเมินและผลที่ได้คือค่าคงที่จำนวนเต็ม


ไม่มี VLA ใน C ++
LF

-2

sizeof() ผู้ประกอบการให้ขนาดของประเภทข้อมูลเท่านั้นมันไม่ได้ประเมินองค์ประกอบด้านใน


นี่เป็นเท็จตัวsizeof()ดำเนินการจะเรียกซ้ำแล้วซ้ำอีกและจะมีขนาดเป็นไบต์ขององค์ประกอบทั้งหมดของคอนเทนเนอร์สมาชิกของคลาสหรือโครงสร้างเป็นต้นคุณสามารถพิสูจน์ได้ด้วยตัวคุณเองได้อย่างง่ายดายด้วยการสร้างคลาสง่ายๆด้วยสมาชิกเพียงไม่กี่คนและ เรียกsizeof()มัน (อย่างไรก็ตามมีอะไรในนั้นที่เป็นตัวชี้ที่ไม่สามารถมองเห็นขนาดของ - เพียงแค่ขนาดของตัวชี้) สิ่งนี้เกิดขึ้นได้ตลอดเวลาในการรวบรวมตามที่ผู้แสดงความเห็นคนอื่นระบุ: การแสดงออกภายใน the sizeof()ไม่ได้รับการประเมิน
Tyler Shellberg
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.