ใน C และ C ++ วิธีการใดที่สามารถป้องกันการใช้งานมอบหมาย (=) โดยไม่ได้ตั้งใจโดยที่จำเป็นต้องมีความเท่าเทียมกัน (==)


15

ใน C และ C ++ มันง่ายมากที่จะเขียนโค้ดต่อไปนี้พร้อมกับข้อผิดพลาดร้ายแรง

char responseChar = getchar();
int confirmExit = 'y' == tolower(responseChar);
if (confirmExit = 1)
{
    exit(0);
}

ข้อผิดพลาดคือคำสั่ง if ควรได้รับ:

if (confirmExit == 1)

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

มีวิธีที่ดีในการป้องกันข้อผิดพลาดประเภทนี้หรือไม่?


39
ใช่. เปิดคำเตือนของคอมไพเลอร์ ถือว่าคำเตือนเป็นข้อผิดพลาดและไม่ใช่ปัญหา
มาร์ตินนิวยอร์ก


9
ในตัวอย่างที่กำหนดวิธีแก้ปัญหานั้นง่าย if (confirmExit)คุณกำหนดค่าบูลีนจึงใช้มันเป็นแบบบูล:
รักษาความปลอดภัย

4
ปัญหาคือความผิดพลาดเกิดขึ้นจากภาษา C "นักออกแบบ" เมื่อพวกเขาเลือกที่จะใช้ = สำหรับผู้ดำเนินการที่ได้รับมอบหมายและ == สำหรับการเปรียบเทียบความเท่าเทียมกัน ALGOL ใช้: = เนื่องจากพวกเขาต้องการใช้เฉพาะ = สำหรับการเปรียบเทียบความเท่าเทียมกันและ PASCAL และ Ada ตามการตัดสินใจของ ALGOL (เป็นที่น่าสังเกตว่าเมื่อกระทรวงแจ้งการเข้าร่วม C-based ใน DoD1 bake-off ซึ่งในที่สุดก็ให้ Ada, Bell Labs ปฏิเสธโดยกล่าวว่า "C ไม่ใช่ตอนนี้และจะไม่แข็งแกร่งพอสำหรับภารกิจที่สำคัญของกระทรวง ซอฟต์แวร์ "หนึ่งความปรารถนาที่กระทรวงและผู้รับเหมาได้ฟัง Bell Labs เกี่ยวกับเรื่องนี้)
John R. Strohm

4
@John ตัวเลือกสัญลักษณ์ไม่ใช่ปัญหา แต่เป็นความจริงที่ว่าการมอบหมายนั้นเป็นนิพจน์ที่ส่งกลับค่าที่กำหนดซึ่งอนุญาตให้มีเงื่อนไขใดก็ได้a = bหรือa == bภายในเงื่อนไขก็ได้
Karl Bielefeldt

คำตอบ:


60

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

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

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

คำเตือนเป็นข้อผิดพลาดเชิงตรรกะจริง ๆ ในรหัสและควรได้รับการแก้ไข


2
ไปกับคำเตือนอย่างแน่นอนไม่ใช่เงื่อนไข Yoda - คุณสามารถลืมค่าคงที่ตามเงื่อนไขก่อนได้อย่างง่ายดายเท่าที่คุณจะลืมทำ ==
Michael Kohne

8
ฉันประหลาดใจมากที่คำตอบที่โหวตมากที่สุดสำหรับคำถามนี้คือ 'เปลี่ยนคำเตือนของคอมไพเลอร์เป็นข้อผิดพลาด' ฉันคาดหวังความif (0 == ret)สยองขวัญ
James

2
@James: ... ไม่ต้องพูดถึงมันจะไม่ทำงานa == b!!
Emilio Garavaglia

6
@EmilioGaravaglia: มีวิธีแก้ปัญหาง่ายๆคือ: เพียงแค่เขียน0==a && 0==b || 1==a && 1==b || 2==a && 2==b || ...(ทำซ้ำสำหรับค่าที่เป็นไปได้ทั้งหมด) อย่าลืม...|| 22==a && 22==b || 23==a && 24==b || 25==a && 25==b ||ข้อผิดพลาดที่จำเป็น... มิฉะนั้นโปรแกรมเมอร์ผู้บำรุงรักษาจะไม่สนุก
281377

10

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


1
ทำง่ายเมื่อพฤติกรรมของโปรแกรมของคุณถูกกำหนดไว้อย่างสมบูรณ์
James

3
ปัญหานี้อาจทดสอบได้ยาก ฉันเคยเห็นคนถูกกัดrc=MethodThatRarelyFails(); if(rc = SUCCESS){มากกว่าหนึ่งครั้งโดยเฉพาะอย่างยิ่งหากวิธีการล้มเหลวเฉพาะในสภาพที่ยากที่จะทดสอบ
Gort the Robot

3
@StevenBurnap: นั่นคือสิ่งที่เป็นวัตถุจำลอง การทดสอบที่เหมาะสมรวมถึงการทดสอบโหมดความล้มเหลว
Jan Hudec

นี่คือคำตอบที่ถูกต้อง
การแข่งขัน Lightness กับโมนิก้า

6

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

if (confirmExit = 1)  // Unsafe

if (1=confirmExit)    // Safe and detected at compile time.

คอมไพเลอร์จะรายงานข้อผิดพลาดสำหรับการมอบหมายที่ผิดกฎหมายให้คงที่คล้ายกับต่อไปนี้

.\confirmExit\main.cpp:15: error: C2106: '=' : left operand must be l-value

การแก้ไขหากเงื่อนไขจะเป็น:

  if (1==confirmExit)    

ดังที่แสดงโดยความคิดเห็นด้านล่างสิ่งนี้ถือว่าเป็นสิ่งที่ไม่เหมาะสม


8
หรือที่รู้จักในชื่อ Yoda conditionals: wiert.me/2010/05/25/…และstackoverflow.com/questions/8591182/… และprogrammers.stackexchange.com/questions/16908/ …
Martin York

13
ควรสังเกตว่าการทำสิ่งต่าง ๆ ในลักษณะนี้จะทำให้คุณไม่เป็นที่นิยม
riwalk

11
กรุณาอย่าแนะนำการปฏิบัตินี้
James

5
นอกจากนี้ยังใช้งานไม่ได้หากคุณต้องการเปรียบเทียบตัวแปรสองตัวต่อกัน
dan04

บางภาษาอนุญาตให้กำหนด 1 เป็นค่าใหม่ได้ (ใช่ฉันรู้ว่า Q คือการแข่งขัน c / c ++)
Matsemann

4

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


ฉันไม่เห็นด้วย. โดยเฉพาะอย่างยิ่ง = แทนที่จะเป็น == สามารถลื่นไหลได้ง่ายโดยการตรวจสอบรหัส
Simon

2

ขั้นแรกการเพิ่มระดับการเตือนของคุณจะไม่เจ็บ

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

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

หากคุณตั้งใจจะทดสอบผลลัพธ์ของการมอบหมายอย่างไรก็ตามการใช้คำเตือนที่สูงกว่าอาจ [อาจจะ] จับการมอบหมายให้คงที่


ฉันยกคิ้วที่ไม่เชื่อในโปรแกรมเมอร์ที่ไม่ใช่มือใหม่ผู้เห็นเงื่อนไขโยดาและไม่เข้าใจในทันที มันเป็นแค่เรื่องอ่านไม่ยากและแน่นอนว่าไม่เลวเท่าที่ความคิดเห็นบางส่วนอ้างว่า
underscore_d

@underscore_d ที่นายจ้างส่วนใหญ่ของฉันการมอบหมายงานภายในเงื่อนไขได้รับการขมวดคิ้ว คิดว่าเป็นการดีกว่าที่จะแยกการบ้านออกจากเงื่อนไข เหตุผลที่ชัดเจนแม้ค่าใช้จ่ายของรหัสอีกบรรทัดก็เป็นปัจจัยทางวิศวกรรมที่ยั่งยืน ฉันทำงานในสถานที่ที่มีฐานรหัสขนาดใหญ่และมีการบำรุงรักษาจำนวนมาก ฉันเข้าใจว่าบางคนอาจต้องการกำหนดค่าและสาขาตามเงื่อนไขที่เกิดจากการกำหนด ฉันเห็นว่ามันถูกทำบ่อยขึ้นใน Perl และหากเจตนาชัดเจนฉันจะทำตามรูปแบบการออกแบบของผู้แต่ง
octopusgrabbus

ฉันกำลังคิดเกี่ยวกับเงื่อนไขโยดาเพียงอย่างเดียว (เช่นการสาธิตของคุณที่นี่) ไม่ใช่การมอบหมาย (เหมือนใน OP) ฉันไม่รังเกียจอดีต แต่ไม่ชอบคนหลังเหมือนกัน รูปแบบเดียวที่ฉันใช้อย่างจงใจคือif ( auto myPtr = dynamic_cast<some_ptr>(testPtr) ) {มันหลีกเลี่ยงการไร้ประโยชน์nullptrถ้าการร่ายล้มเหลว - ซึ่งน่าจะเป็นเหตุผลว่า C ++ มีความสามารถ จำกัด นี้ในการกำหนดภายในเงื่อนไข สำหรับส่วนที่เหลือใช่คำจำกัดความควรจะได้แนวของตัวเองฉันจะบอกว่า - ง่ายกว่ามากที่จะเห็นภาพรวมและมีแนวโน้มน้อยที่จะลดการจัดเรียงของจิตใจ
underscore_d

@underscore_d แก้ไขคำตอบตามความคิดเห็นของคุณ จุดดี.
octopusgrabbus

1

ช้าไปงานเลี้ยงเช่นเคย แต่การวิเคราะห์รหัสคงที่เป็นกุญแจสำคัญที่นี่

IDEs ส่วนใหญ่ในขณะนี้ให้ SCA มากกว่าการตรวจสอบวากยสัมพันธ์ของคอมไพเลอร์และเครื่องมืออื่น ๆ ที่มีอยู่รวมถึงที่ใช้ MISRA (*) และ / หรือแนวทาง CERT-C

คำแถลง: ฉันเป็นส่วนหนึ่งของคณะทำงาน MISRA C แต่ฉันโพสต์ในฐานะส่วนตัว ฉันยังเป็นอิสระจากผู้จำหน่ายเครื่องมือใด ๆ


-2

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


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

ตรวจสอบหนังสือ "การเขียนรหัสแข็ง" amazon.com/Writing-Solid-Microsoft-Programming-Series/dp/... มันเริ่มต้นด้วยการสนทนาที่ยอดเยี่ยมเกี่ยวกับคอมไพเลอร์และประโยชน์ที่เราจะได้รับจากข้อความเตือนและการวิเคราะห์ที่ครอบคลุมยิ่งขึ้น
DeveloperDon

@KristofProvost ฉันได้รับ visual Studio เพื่อให้ 10 คำเตือนเดียวกันแน่นอนรหัสบรรทัดเดียวกันจากนั้นเมื่อปัญหานี้ถูก 'แก้ไข' มันส่งผลให้ 10 คำเตือนที่เหมือนกันเกี่ยวกับบรรทัดเดียวกันของรหัสบอกว่า วิธีดั้งเดิมดีกว่า
Inverted Llama
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.