ข้อควรระวังด้านความปลอดภัยของชนิด
ความเสี่ยงของฟังก์ชันรับอาร์กิวเมนต์ไม่จำกัด
ข้อควรระวังด้านความปลอดภัยของชนิด เป็นบทเรียน C Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน C Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส C Academy มีบทเรียนทั้งหมด 4 บทเรียน
อาร์กิวเมนต์แบบแปรผันข้ามการตรวจสอบชนิดข้อมูล
ฟังก์ชัน C ปกติจะตรวจสอบว่าอาร์กิวเมนต์ตรงกับชนิดของพารามิเตอร์หรือไม่ แต่อาร์กิวเมนต์แบบแปรผันจะ ข้ามการตรวจสอบนี้ทั้งหมด
คอมไพเลอร์ไม่สามารถตรวจสอบสิ่งที่คุณส่งหลัง ... ได้ จึงทำให้เกิดข้อผิดพลาดได้ง่ายและอันตราย
#include <stdio.h>
#include <stdarg.h>
int add(int n, ...) {
va_list a; va_start(a, n);
int s = 0;
for (int i = 0; i < n; i++) s += va_arg(a, int);
va_end(a);
return s;
}
int main(void) {
printf("%d\n", add(2, 3, 4));
return 0;
}ชนิดข้อมูลผิดใน va_arg
หากอ่านอาร์กิวเมนต์ด้วยชนิดข้อมูลที่ไม่ถูกต้อง ผลลัพธ์จะเป็น พฤติกรรมที่ไม่กำหนด
การส่ง int แต่ไปอ่านเป็น double จะตีความไบต์ที่ไม่เกี่ยวข้องใหม่ และอาจให้ข้อมูลมั่วหรือทำให้โปรแกรมหยุดทำงาน
#include <stdio.h>
#include <stdarg.h>
int read_int(int n, ...) {
va_list a; va_start(a, n);
int v = va_arg(a, int);
va_end(a);
return v;
}
int main(void) {
printf("%d\n", read_int(1, 100));
return 0;
}สตริงรูปแบบไม่ตรงกัน
ในฟังก์ชันรูปแบบเดียวกับ printf การใช้ตัวระบุผิดเป็นข้อผิดพลาดที่พบได้บ่อย printf("%d", 3.14) จะอ่านค่า double ราวกับเป็น int
คอมไพเลอร์สมัยใหม่จะแจ้งเตือนเมื่อพบความไม่ตรงกันในค่าคงที่ แต่ไม่สามารถตรวจจับสตริงรูปแบบที่สร้างขึ้นขณะโปรแกรมทำงานได้
#include <stdio.h>
int main(void) {
double pi = 3.14;
printf("%f\n", pi);
return 0;
}ความประหลาดใจจากการเลื่อนระดับจำนวนเต็ม
ชนิดข้อมูลขนาดเล็กจะถูกเลื่อนระดับเป็น int ในส่วนอาร์กิวเมนต์แบบแปรผัน ดังนั้นแม้คุณจะส่ง char ก็ต้องอ่านกลับเป็น int
การอ่านเป็น char ด้วย va_arg(a, char) เป็นพฤติกรรมที่ไม่กำหนด
#include <stdio.h>
#include <stdarg.h>
void show(int n, ...) {
va_list a; va_start(a, n);
int c = va_arg(a, int);
printf("char is %c\n", c);
va_end(a);
}
int main(void) {
show(1, 'A');
return 0;
}Float กลายเป็น double
อาร์กิวเมนต์ชนิด float จะถูกเลื่อนระดับเป็น double เสมอในอาร์กิวเมนต์แบบแปรผัน คุณต้องอ่านเป็น double และห้ามอ่านเป็น float
นี่เป็นหนึ่งในข้อผิดพลาดของอาร์กิวเมนต์แบบแปรผันที่พบบ่อยที่สุด
#include <stdio.h>
#include <stdarg.h>
void show(int n, ...) {
va_list a; va_start(a, n);
double d = va_arg(a, double);
printf("%.2f\n", d);
va_end(a);
}
int main(void) {
float f = 1.5f;
show(1, f);
return 0;
}การอ่านอาร์กิวเมนต์มากเกินไป
หากโค้ดเรียก va_arg มากกว่าจำนวนอาร์กิวเมนต์ที่ผู้เรียกส่งมา คุณจะอ่าน ข้อมูลมั่ว จากสแตก
ด้วยเหตุนี้จึงจำเป็นต้องระบุจำนวนให้ถูกต้องหรือใช้เครื่องหมายสิ้นสุด
#include <stdio.h>
#include <stdarg.h>
int sum(int n, ...) {
va_list a; va_start(a, n);
int s = 0;
for (int i = 0; i < n; i++) s += va_arg(a, int);
va_end(a);
return s;
}
int main(void) {
printf("%d\n", sum(3, 1, 2, 3));
return 0;
}ต้องระวังเครื่องหมายสิ้นสุดที่เป็นพอยน์เตอร์ NULL
การใช้ NULL เป็นเครื่องหมายสิ้นสุดมีความเสี่ยง: บนแพลตฟอร์มบางชนิด NULL คือ 0 ที่เป็น int ซึ่งมีขนาดต่างจากพอยน์เตอร์
แปลงชนิดอย่างชัดเจน เช่น (char *)NULL เพื่อให้แน่ใจว่ามีการส่งชนิดข้อมูลที่ถูกต้อง
#include <stdio.h>
#include <stdarg.h>
void print_all(const char *first, ...) {
va_list a; va_start(a, first);
const char *s = first;
while (s) { printf("%s\n", s); s = va_arg(a, const char *); }
va_end(a);
}
int main(void) {
print_all("a", "b", (char *)NULL);
return 0;
}แอตทริบิวต์รูปแบบของคอมไพเลอร์
GCC และ Clang มี __attribute__((format(printf, 1, 2))) เพื่อบอกคอมไพเลอร์ว่าฟังก์ชันของคุณใช้การจัดรูปแบบแบบ printf
จากนั้นคอมไพเลอร์จะตรวจสอบสตริงรูปแบบขณะคอมไพล์และตรวจพบข้อผิดพลาดได้หลายประเภท
#include <stdio.h>
#include <stdarg.h>
void logf(const char *fmt, ...)
__attribute__((format(printf, 1, 2)));
void logf(const char *fmt, ...) {
va_list a; va_start(a, fmt);
vprintf(fmt, a);
va_end(a);
}
int main(void) {
logf("%d items\n", 3);
return 0;
}เลือกทางเลือกที่ปลอดภัยกว่า
เนื่องจากอาร์กิวเมนต์แบบแปรผันมีโอกาสเกิดข้อผิดพลาดได้ง่าย ให้เลือกการออกแบบที่ตรวจสอบชนิดข้อมูลได้เมื่อเป็นไปได้: ส่งอาร์เรย์ สตรักต์ หรือพารามิเตอร์แบบตายตัวอย่างชัดเจน
สงวนการใช้อาร์กิวเมนต์แบบแปรผันไว้สำหรับกรณีที่ต้องการรูปแบบเดียวกับ printf อย่างแท้จริง
#include <stdio.h>
int sum_array(const int *a, int n) {
int s = 0;
for (int i = 0; i < n; i++) s += a[i];
return s;
}
int main(void) {
int nums[] = {1, 2, 3, 4};
printf("%d\n", sum_array(nums, 4));
return 0;
}จัดทำเอกสารข้อตกลง
เนื่องจากคอมไพเลอร์ไม่สามารถบังคับใช้ชนิดของอาร์กิวเมนต์แบบแปรผันได้ เอกสารที่ชัดเจนจึงเป็นเกราะป้องกันของคุณ
ระบุให้แน่ชัดว่าผู้เรียกต้องส่งชนิดข้อมูลและอาร์กิวเมนต์กี่ตัว รวมถึงรายการสิ้นสุดลงอย่างไร
#include <stdio.h>
#include <stdarg.h>
/* sum_ints(n, ...): pass exactly n int values */
int sum_ints(int n, ...) {
va_list a; va_start(a, n);
int s = 0;
for (int i = 0; i < n; i++) s += va_arg(a, int);
va_end(a);
return s;
}
int main(void) {
printf("%d\n", sum_ints(2, 5, 5));
return 0;
}การทดสอบอย่างละเอียด
เนื่องจากข้อผิดพลาดอาจซ่อนอยู่โดยไม่แสดงอาการ ให้ทดสอบฟังก์ชันแบบแปรผันด้วยจำนวนและชนิดอาร์กิวเมนต์ที่หลากหลาย
กรณีขอบเขต เช่น ไม่มีอาร์กิวเมนต์หรือใช้จำนวนสูงสุด มักช่วยเปิดเผยข้อผิดพลาดได้
#include <stdio.h>
#include <stdarg.h>
int sum(int n, ...) {
va_list a; va_start(a, n);
int s = 0;
for (int i = 0; i < n; i++) s += va_arg(a, int);
va_end(a);
return s;
}
int main(void) {
printf("%d\n", sum(0));
printf("%d\n", sum(1, 9));
return 0;
}ตรวจสอบความเข้าใจอย่างรวดเร็ว
ทดสอบความเข้าใจเกี่ยวกับความเสี่ยงด้านชนิดข้อมูลของอาร์กิวเมนต์แบบแปรผัน
สรุปทบทวน
คุณได้เรียนรู้ข้อควรระวังของฟังก์ชันแบบแปรผัน:
- คอมไพเลอร์ ไม่ตรวจสอบชนิดของอาร์กิวเมนต์แบบแปรผัน
- ชนิดข้อมูล จำนวนอาร์กิวเมนต์ หรือเครื่องหมายสิ้นสุดที่ไม่ถูกต้องทำให้เกิดพฤติกรรมที่ไม่กำหนด
- จำการเลื่อนระดับไว้: จำนวนเต็มขนาดเล็กเป็น
intและfloatเป็นdouble - ใช้ แอตทริบิวต์รูปแบบของคอมไพเลอร์ จัดทำเอกสารข้อตกลง และเลือกทางเลือกที่ตรวจสอบชนิดข้อมูลได้เมื่อทำได้
เรียนรู้ C ด้วย AI tutor — ฟรี
เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป
- คอร์ส
- 39
- บทเรียน
- 144
คำถามที่พบบ่อย
บทเรียน “ข้อควรระวังด้านความปลอดภัยของชนิด” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “ข้อควรระวังด้านความปลอดภัยของชนิด” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส C Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส C Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “ข้อควรระวังด้านความปลอดภัยของชนิด”
ความเสี่ยงของฟังก์ชันรับอาร์กิวเมนต์ไม่จำกัด คุณปฏิบัติ C Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน C Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน C Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน
บทเรียน “ข้อควรระวังด้านความปลอดภัยของชนิด” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน C Academy นี้ได้ไหม
ได้ บทเรียน C Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- มาโคร stdarg
- การเขียนฟังก์ชันแบบ printf
- ข้อควรระวังด้านความปลอดภัยของชนิด
- ตัวอย่างการใช้งานจริง