อย่างที่คนอื่นพูดถึงปัญหาไม่ได้อยู่ที่goto
ตัวของมันเอง ปัญหาเกิดจากการใช้งานของผู้ใช้goto
และวิธีการทำให้รหัสเข้าใจและบำรุงรักษาได้ยากขึ้น
สมมติตัวอย่างรหัสต่อไปนี้:
i = 4;
label: printf( "%d\n", i );
มูลค่าที่ได้รับการพิมพ์สำหรับi
? พิมพ์เมื่อไหร่ ? จนกว่าคุณจะบัญชีสำหรับทุกตัวอย่างgoto label
ในการทำงานของคุณคุณไม่สามารถรู้ได้ สถานะที่เรียบง่ายของฉลากนั้นทำลายความสามารถในการตรวจแก้จุดบกพร่องของรหัสด้วยการตรวจสอบอย่างง่าย สำหรับฟังก์ชั่นขนาดเล็กที่มีหนึ่งหรือสองสาขาไม่ใช่ปัญหามาก สำหรับฟังก์ชั่นที่ไม่เล็ก ...
ย้อนกลับไปในช่วงต้นทศวรรษ 90 เราได้รับโค้ด C จำนวนมากซึ่งผลักดันการแสดงผลกราฟิก 3 มิติและบอกให้ทำงานเร็วขึ้น มันเป็นโค้ดประมาณ 5,000 บรรทัดเท่านั้น แต่ทั้งหมดนั้นอยู่ในmain
นั้นและผู้เขียนใช้ประมาณ 15 หรือมากกว่านั้นgoto
ในการแยกทั้งสองทิศทาง นี่คือรหัสที่ไม่ดีที่จะเริ่มต้นด้วย แต่การปรากฏตัวของเหล่านั้นgoto
ทำให้มันแย่ลงมาก เพื่อนร่วมงานของฉันใช้เวลาประมาณ 2 สัปดาห์เพื่อไขปริศนาเรื่องการควบคุม ยิ่งไปกว่านั้นgoto
ผลลัพธ์เหล่านั้นส่งผลให้เกิดโค้ดควบคู่ไปกับตัวเองอย่างแน่นหนาซึ่งเราไม่สามารถเปลี่ยนแปลงได้หากไม่ทำอะไรเลย
เราพยายามรวบรวมด้วยการเพิ่มประสิทธิภาพระดับ 1 และคอมไพเลอร์กิน RAM ทั้งหมดที่มีอยู่จากนั้นสลับที่มีอยู่ทั้งหมดแล้วตกใจระบบ (ซึ่งอาจไม่มีส่วนเกี่ยวข้องกับgoto
ตัวเอง แต่ฉันชอบทิ้งเรื่องเล็ก ๆ น้อย ๆ ไว้ที่นั่น)
ในที่สุดเราให้ลูกค้าสองตัวเลือก - ให้เราเขียนสิ่งใหม่ทั้งหมดตั้งแต่เริ่มต้นหรือซื้อฮาร์ดแวร์ที่เร็วขึ้น
พวกเขาซื้อฮาร์ดแวร์เร็วขึ้น
กฎของ Bode สำหรับการใช้งานgoto
:
- สาขาไปข้างหน้าเท่านั้น
- อย่าบายพาสโครงสร้างการควบคุม (เช่นไม่สาขาเข้าไปในร่างกายของนั้น
if
หรือfor
หรือwhile
คำสั่ง);
- อย่าใช้
goto
แทนโครงสร้างการควบคุม
มีหลายกรณีที่มีgoto
เป็นคำตอบที่ถูก แต่จะหายาก (ทำลายออกจากวงซ้อนกันอย่างลึกซึ้งเป็นเรื่องเกี่ยวกับสถานที่เดียวที่ฉันต้องการใช้)
แก้ไข
goto
ขยายตัวในคำสั่งสุดท้ายที่นี่เป็นหนึ่งในไม่กี่กรณีการใช้งานที่ถูกต้องสำหรับ สมมติว่าเรามีฟังก์ชั่นต่อไปนี้:
T ***myalloc( size_t N, size_t M, size_t P )
{
size_t i, j, k;
T ***arr = malloc( sizeof *arr * N );
for ( i = 0; i < N; i ++ )
{
arr[i] = malloc( sizeof *arr[i] * M );
for ( j = 0; j < M; j++ )
{
arr[i][j] = malloc( sizeof *arr[i][j] * P );
for ( k = 0; k < P; k++ )
arr[i][j][k] = initial_value();
}
}
return arr;
}
ตอนนี้เรามีปัญหา - จะเกิดอะไรขึ้นถ้ามีการmalloc
โทรสายใดสายหนึ่งล้มเหลวตรงกลาง ไม่น่าจะเป็นเหตุการณ์ที่อาจเกิดขึ้นเราไม่ต้องการส่งคืนอาเรย์ที่ถูกจัดสรรบางส่วนและเราไม่ต้องการประกันตัวฟังก์ชันที่มีข้อผิดพลาด เราต้องการล้างข้อมูลหลังจากเราและจัดสรรคืนหน่วยความจำที่จัดสรรไว้บางส่วน ในภาษาที่มีข้อยกเว้นในการจัดสรรที่ไม่ดีนั่นเป็นเรื่องตรงไปตรงมา - คุณเพียงแค่เขียนตัวจัดการข้อยกเว้นเพื่อเพิ่มสิ่งที่จัดสรรไว้แล้ว
ใน C คุณไม่มีการจัดการข้อยกเว้นอย่างเป็นระบบ คุณต้องตรวจสอบค่าตอบแทนของการmalloc
โทรแต่ละครั้งและดำเนินการที่เหมาะสม
T ***myalloc( size_t N, size_t M, size_t P )
{
size_t i, j, k;
T ***arr = malloc( sizeof *arr * N );
if ( arr )
{
for ( i = 0; i < N; i ++ )
{
if ( !(arr[i] = malloc( sizeof *arr[i] * M )) )
goto cleanup_1;
for ( j = 0; j < M; j++ )
{
if ( !(arr[i][j] = malloc( sizeof *arr[i][j] * P )) )
goto cleanup_2;
for ( k = 0; k < P; k++ )
arr[i][j][k] = initial_value();
}
}
}
goto done;
cleanup_2:
// We failed while allocating arr[i][j]; clean up the previously allocated arr[i][j]
while ( j-- )
free( arr[i][j] );
free( arr[i] );
// fall through
cleanup_1:
// We failed while allocating arr[i]; free up all previously allocated arr[i][j]
while ( i-- )
{
for ( j = 0; j < M; j++ )
free( arr[i][j] );
free( arr[i] );
}
free( arr );
arr = NULL;
done:
return arr;
}
เราสามารถทำสิ่งนี้ได้โดยไม่ใช้goto
? แน่นอนว่าเราทำได้ - เพียงแค่ต้องการการทำบัญชีเพิ่มเติมเล็กน้อย (และในทางปฏิบัตินั่นคือเส้นทางที่ฉันจะทำ) แต่หากคุณกำลังมองหาสถานที่ที่ใช้งานgoto
ไม่ได้เป็นสัญญาณของการฝึกฝนหรือการออกแบบที่ไม่ดีในทันทีนี่เป็นหนึ่งในไม่กี่คน