ถูกต้อง แต่ไม่มีค่าไวยากรณ์ในตัวพิมพ์สลับหรือไม่


207

จากการพิมพ์ผิดเล็กน้อยฉันพบโครงสร้างนี้โดยบังเอิญ:

int main(void) {
    char foo = 'c';

    switch(foo)
    {
        printf("Cant Touch This\n");   // This line is Unreachable

        case 'a': printf("A\n"); break;
        case 'b': printf("B\n"); break;
        case 'c': printf("C\n"); break;
        case 'd': printf("D\n"); break;
    }

    return 0;
}

ดูเหมือนว่าprintfที่ด้านบนของswitchคำสั่งนั้นถูกต้อง แต่ก็ไม่สามารถเข้าถึงได้อย่างสมบูรณ์

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

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


51
GCC มีการตั้งค่าสถานะพิเศษสำหรับสิ่งนี้ มันคือ-Wswitch-unreachable
Eli Sadoff

57
"สิ่งนี้มีจุดประสงค์หรือไม่?" คุณสามารถgotoเข้าและออกจากส่วนที่ไม่สามารถเข้าถึงได้ซึ่งอาจเป็นประโยชน์สำหรับแฮ็กต่างๆ
HolyBlackCat

12
@HolyBlackCat จะไม่เป็นเช่นนั้นสำหรับรหัสที่ไม่สามารถเข้าถึงได้ทั้งหมดหรือไม่
Eli Sadoff

28
@EliSadoff แน่นอน ฉันเดาว่ามันไม่ได้ตอบสนองวัตถุประสงค์พิเศษใด ๆ ฉันพนันได้ว่าได้รับอนุญาตเพียงเพราะไม่มีเหตุผลที่จะห้ามมัน หลังจากที่ทุกคนswitchเป็นเพียงเงื่อนไขgotoที่มีป้ายชื่อหลาย มีข้อ จำกัด ที่เหมือนกันไม่มากก็น้อยเช่นเดียวกับที่คุณมีในโค้ดปกติที่เต็มไปด้วยฉลากของ goto
HolyBlackCat

16
น่าจะชี้ให้เห็นว่าตัวอย่างของ @MooingDuck เป็นตัวแปรในอุปกรณ์ของ Duff ( en.wikipedia.org/wiki/Duff's_device )
Michael Anderson

คำตอบ:


226

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

switch (foo)
{
    int i;
case 0:
    i = 0;
    //....
case 1:
    i = 1;
    //....
}

มาตรฐาน ( N1579 6.8.4.2/7) มีตัวอย่างต่อไปนี้:

ตัวอย่างในส่วนของโปรแกรมประดิษฐ์

switch (expr)
{
    int i = 4;
    f(i);
case 0:
    i = 17;
    /* falls through into default code */
default:
    printf("%d\n", i);
}

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

PS BTW ตัวอย่างไม่ถูกต้องรหัส C ++ ในกรณีนั้น ( N4140 6.7/3เน้นการเน้นของฉัน):

โปรแกรมที่กระโดด90จากจุดที่ตัวแปรที่มีระยะเวลาการจัดเก็บอัตโนมัติไม่อยู่ในขอบเขตไปยังจุดที่อยู่ในขอบเขตจะเกิดขึ้นไม่ดีเว้นแต่ตัวแปรนั้นมีประเภทสเกลาร์ , ประเภทคลาสที่มีคอนสตรัคค่าเริ่มต้นเล็กน้อย เวอร์ชันที่ผ่านการรับรอง cv ของหนึ่งในประเภทเหล่านี้หรืออาร์เรย์หนึ่งในชนิดก่อนหน้านี้และถูกประกาศโดยไม่มี initializer (8.5)


90) การโอนจากเงื่อนไขของswitchคำสั่งไปยังฉลากกรณีถือว่าเป็นการกระโดดในส่วนนี้

ดังนั้นการแทนที่int i = 4;ด้วยint i;ทำให้มันเป็น C ++ ที่ถูกต้อง


3
"... แต่ไม่ได้เริ่มต้น ... " ดูเหมือนว่าiจะเริ่มต้นได้ถึง 4 ฉันหายไปอะไร
yano

7
โปรดทราบว่าหากตัวแปรคือstaticมันจะถูกเริ่มต้นเป็นศูนย์จึงมีการใช้งานที่ปลอดภัยสำหรับสิ่งนี้เช่นกัน
Leushenko

23
@yano เรามักจะข้ามการกำหนดi = 4;ค่าเริ่มต้นดังนั้นจึงไม่เกิดขึ้น
AlexD

12
แน่นอน! ... คำถามทั้งหมด ... geez ความปรารถนาดีในการลบความโง่เขลานี้
yano

1
ดี! บางครั้งฉันต้องการตัวแปรชั่วคราวภายใน a caseและต้องใช้ชื่อที่แตกต่างกันในแต่ละชื่อcaseหรือกำหนดไว้นอกสวิตช์
SJuan76

98

สิ่งนี้มีจุดประสงค์หรือไม่?

ใช่. หากคุณไม่ใส่คำแถลงคุณต้องวางคำแถลงไว้ก่อนหน้าป้ายกำกับแรกซึ่งจะทำให้รู้สึกว่าสมบูรณ์:

switch (a) {
  int i;
case 0:
  i = f(); g(); h(i);
  break;
case 1:
  i = g(); f(); h(i);
  break;
}

กฎสำหรับการประกาศและคำสั่งนั้นใช้ร่วมกันสำหรับบล็อกโดยทั่วไปดังนั้นจึงเป็นกฎเดียวกันกับที่อนุญาตให้มีคำสั่งที่นั่นด้วย


ถ้าพูดถึงประโยคแรกคือโครงสร้างลูปฉลากเคสอาจปรากฏในเนื้อความลูป:

switch (i) {
  for (;;) {
    f();
  case 1:
    g();
  case 2:
    if (h()) break;
  }
}

โปรดอย่าเขียนรหัสเช่นนี้หากมีวิธีการเขียนที่อ่านง่ายขึ้น แต่มันใช้ได้อย่างสมบูรณ์และสามารถf()โทรได้


@MatthieuM Duff's Device มีเลเบล case ในลูป แต่เริ่มต้นด้วย case label ก่อนลูป

2
ฉันไม่แน่ใจว่าฉันควร upvote สำหรับตัวอย่างที่น่าสนใจหรือ downvote สำหรับความบ้าคลั่งที่สุดของการเขียนในโปรแกรมจริง :) ขอแสดงความยินดีสำหรับการดำดิ่งสู่เหวและกลับมาอีกครั้งในชิ้นเดียว
Liviu T.

@ChemicalEngineer: หากรหัสเป็นส่วนหนึ่งของลูปเนื่องจากมันอยู่ในอุปกรณ์ของ Duff { /*code*/ switch(x) { } }อาจดูสะอาดขึ้น แต่ก็ผิดเช่นกัน
Ben Voigt

39

มีการใช้ที่มีชื่อเสียงของสิ่งนี้เรียกว่า อุปกรณ์ของดัฟฟ์

int n = (count+3)/4;
switch (count % 4) {
  do {
    case 0: *to = *from++;
    case 3: *to = *from++;
    case 2: *to = *from++;
    case 1: *to = *from++;
  } while (--n > 0);
}

ที่นี่เราคัดลอกบัฟเฟอร์ชี้ไปตามการบัฟเฟอร์ชี้ไปตามfrom toเราคัดลอกcountอินสแตนซ์ของข้อมูล

do{}while()คำสั่งเริ่มต้นก่อนคนแรกที่caseป้ายและป้ายชื่อที่ฝังอยู่ภายในcasedo{}while()

สิ่งนี้จะช่วยลดจำนวนสาขาที่มีเงื่อนไขที่ส่วนท้ายของdo{}while()ลูปที่พบโดยปัจจัย 4 อย่างคร่าว ๆ (ในตัวอย่างนี้ค่าคงที่สามารถปรับได้ตามมูลค่าที่คุณต้องการ)

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

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

gotoนอกจากนี้ในการควบคุมการไหลที่ไม่ได้เปลี่ยนเฉพาะได้รับคุณเป็นส่วนหนึ่งของสวิทช์บล็อกที่เป็นภาพประกอบข้างต้นหรือมี


2
ของหลักสูตรนี้จะยังคงเป็นไปได้โดยไม่อนุญาตให้ข้อความข้างต้นกรณีแรกเป็นคำสั่งของdo {และไม่ว่าทั้งสองทำหน้าที่ในการวางเป้าหมายการกระโดดในครั้งแรกcase 0: *to = *from++;
Ben Voigt

1
@ BenVoigt ฉันขอยืนยันว่าการวางตำแหน่งdo {นั้นสามารถอ่านได้ง่ายขึ้น ใช่การโต้เถียงเกี่ยวกับความสามารถในการอ่านสำหรับอุปกรณ์ของ Duff นั้นโง่และไม่มีจุดหมายและน่าจะเป็นวิธีที่ง่าย
คดีของกองทุนโมนิกา

1
@QPaysTaxes คุณควรตรวจสอบ Simon Tatham ของCoroutines ใน C หรืออาจจะไม่
Jonas Schäfer

@ JonasSchäferสนุกมากนั่นคือสิ่งที่ C ++ 20 coroutines กำลังทำเพื่อคุณ
Yakk - Adam Nevraumont

15

สมมติว่าคุณใช้ gcc บน Linux จะมีคำเตือนถ้าคุณใช้ 4.4 หรือรุ่นก่อนหน้า

ตัวเลือก -Wunreachable-code ถูกลบใน gcc 4.4เป็นต้นไป


มีประสบการณ์ปัญหามือแรกเสมอช่วย!
16 ตัน

@JonathanLeffler: ปัญหาทั่วไปของคำเตือน gcc ที่ไวต่อชุดการเพิ่มประสิทธิภาพเฉพาะที่เลือกยังคงเป็นความจริงโชคไม่ดีและทำให้ผู้ใช้มีประสบการณ์ที่ไม่ดี มันน่ารำคาญจริง ๆ ที่มี Debug แบบใหม่ตามด้วยการสร้าง Release ที่ล้มเหลว: /
Matthieu M.

@MatthieuM: ดูเหมือนว่าคำเตือนดังกล่าวจะง่ายมากในการตรวจสอบในกรณีที่เกี่ยวข้องกับไวยากรณ์มากกว่าการวิเคราะห์ความหมาย [เช่นรหัสตาม "ถ้า" ซึ่งมีผลตอบแทนในทั้งสองสาขา] และทำให้ง่ายขึ้นที่จะยับยั้ง "รำคาญ" คำเตือน ในทางกลับกันมีบางกรณีที่มีประโยชน์ที่จะมีข้อผิดพลาดหรือคำเตือนในการวางบิลที่ไม่ได้อยู่ในการสร้างการดีบัก ปล่อย).
supercat

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

1
... เพื่อทราบว่าเหตุใดการกำหนดค่าบางอย่างจึงสร้างรหัสที่ใหญ่กว่าตัวอื่น [เช่นเนื่องจากคอมไพเลอร์อาจพิจารณาว่าเงื่อนไขบางอย่างเป็นไปไม่ได้ในการกำหนดค่าหนึ่ง แต่ไม่ใช่แบบอื่น] แต่นั่นไม่ได้หมายความว่ามีอะไรผิดปกติ แฟชั่นนั้นมีการกำหนดค่าบางอย่าง
supercat

11

ไม่เพียง แต่สำหรับการประกาศตัวแปร แต่การกระโดดขั้นสูงเช่นกัน คุณสามารถใช้มันได้ดีถ้าหากว่าคุณไม่ชอบรหัสสปาเก็ตตี้

int main()
{
    int i = 1;
    switch(i)
    {
        nocase:
        printf("no case\n");

        case 0: printf("0\n"); break;
        case 1: printf("1\n"); goto nocase;
    }
    return 0;
}

พิมพ์

1
no case
0 /* Notice how "0" prints even though i = 1 */

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


และอะไรคือความแตกต่างระหว่างnocase:และdefault:?
i486

@ i486 เมื่อมันไม่ได้ทริกเกอร์i=4 nocase
Sanchke Dellowar

@SanchkeDellowar นั่นคือสิ่งที่ฉันหมายถึง
njzk2

ทำไมคนเราถึงทำแบบนั้นแทนที่จะใส่เคส 1 ก่อนหน้าเคส 0 และใช้การเข้าใจผิด?
Jonas Schäfer

@JonasWielicki ในวัตถุประสงค์นี้คุณสามารถทำได้ แต่รหัสนี้เป็นเพียงกรณีแสดงผลของสิ่งที่สามารถทำได้
Sanchke Dellowar

11

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

int n = ...;
int iterations = n/8;
switch(n%8) {
    while(iterations--) {
        sum += *ptr++;
        case 7: sum += *ptr++;
        case 6: sum += *ptr++;
        case 5: sum += *ptr++;
        case 4: sum += *ptr++;
        case 3: sum += *ptr++;
        case 2: sum += *ptr++;
        case 1: sum += *ptr++;
        case 0: ;
    }
}

คุณเห็นรหัสระหว่างswitch(n%8) {และcase 7:ฉลากสามารถเข้าถึงได้อย่างแน่นอน ...


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


การเพิ่มอาร์เรย์ความยาวผันแปรนำไปสู่การกำหนดข้อ จำกัด ทางโครงสร้างที่เกี่ยวข้อง
supercat

@supercat มีข้อ จำกัด อะไรบ้าง?
cmaster - คืนสถานะโมนิกา

1
ทั้งฉลากgotoและไม่switch/case/defaultอาจปรากฏขึ้นภายในขอบเขตของวัตถุหรือประเภทประกาศ สิ่งนี้หมายความว่าถ้าบล็อกมีการประกาศใด ๆ ของออบเจ็กต์หรือความยาวตัวแปรอาเรย์ป้ายใด ๆ จะต้องนำหน้าการประกาศเหล่านั้น มีความสับสนเล็กน้อยของการใช้คำฟุ่มเฟือยในมาตรฐานซึ่งจะแนะนำว่าในบางกรณีขอบเขตของการประกาศ VLA ครอบคลุมไปถึงคำสั่งสวิตช์ทั้งหมด ดูstackoverflow.com/questions/41752072/…สำหรับคำถามของฉัน
supercat

@supercat: คุณเพียงแค่เข้าใจผิดว่าการใช้คำฟุ่มเฟือย (ซึ่งฉันคิดว่าเป็นเหตุผลที่คุณลบคำถามของคุณ) มันกำหนดข้อกำหนดเกี่ยวกับขอบเขตที่อาจกำหนด VLA ไม่ขยายขอบเขตนั้นเพียงทำให้ข้อกำหนด VLA บางอย่างไม่ถูกต้อง
Keith Thompson

@ KeithThompson: ใช่ฉันเข้าใจผิด การใช้กาลปัจจุบันในเชิงอรรถทำให้สิ่งต่าง ๆ เกิดความสับสนและฉันคิดว่าแนวคิดนี้น่าจะแสดงได้ดีกว่าเป็นข้อห้าม: "คำแถลงสวิทช์ที่ร่างกายมีคำประกาศ VLA อยู่ภายในนั้นจะไม่รวมสวิตช์หรือกรณีใด ๆ ขอบเขตของการประกาศ VLA นั้น "
supercat

10

คุณได้คำตอบของคุณเกี่ยวกับตัวเลือกที่จำเป็นgcc -Wswitch-unreachableในการสร้างคำเตือนคำตอบนี้คือการอธิบายเกี่ยวกับการใช้งาน / ความมีค่าควรควร

การอ้างถึงโดยตรงC11ตอนที่§6.8.4.2 (การเน้นของฉัน )

switch (expr)
{
int i = 4;
f(i);
case 0:
i = 17;
/* falls through into default code */
default:
printf("%d\n", i);
}

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

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


9

มีความเป็นไปได้ที่จะใช้ "วนครึ่ง" กับมันแม้ว่ามันอาจจะไม่ใช่วิธีที่ดีที่สุดที่จะทำ:

char password[100];
switch(0) do
{
  printf("Invalid password, try again.\n");
default:
  read_password(password, sizeof(password));
} while (!is_valid_password(password));

@RichardII มันเป็นปุนหรืออะไรนะ? กรุณาอธิบาย.
Dancia

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