C Academy · บทเรียน

ข้อควรระวังด้านความปลอดภัยของชนิด

ความเสี่ยงของฟังก์ชันรับอาร์กิวเมนต์ไม่จำกัด

บทเรียน 3 จาก 413 ขั้นตอน

ข้อควรระวังด้านความปลอดภัยของชนิด เป็นบทเรียน 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. มาโคร stdarg
  2. การเขียนฟังก์ชันแบบ printf
  3. ข้อควรระวังด้านความปลอดภัยของชนิด
  4. ตัวอย่างการใช้งานจริง
← กลับไปที่ C Academy