← Lập trình C cơ bản

Bài 8 · Vận dụng · 20 phút· Cập nhật 11/06/2026

Chuyển đổi kiểu ngầm định

Biên soạn bởi Nguyễn Anh Tuấn

Chuyển đổi kiểu ngầm định (implicit conversion) trong C: thăng cấp số nguyên, mất mát khi gán thu hẹp, bẫy so sánh signed/unsigned - bắt bằng cờ -Wconversion.

Bài 7 bạn đã biết 7 / 2 ra 3, và "muốn 3.5 thì viết 7 / 2.0". Nhưng khoan - 7 là int, 2.0 là double, hai kiểu khác nhau (Bài 5) mà sao cộng trừ nhân chia được? Vì C tự chuyển đổi kiểu ngầm định (implicit conversion). "Ngầm định" không có nghĩa là tuỳ tiện - tất cả theo ba luật:

  • Trộn trong biểu thức: kiểu RỘNG HƠN thắng - int gặp double thì int được nâng lên double.
  • Thăng cấp số nguyên (integer promotion): char và short luôn được nâng lên int trước khi tính.
  • Phép GÁN đi ngược: ép giá trị về kiểu của ĐÍCH - kể cả khi phải cắt bớt (thu hẹp).

Vì sao đáng học riêng một bài

Hai luật đầu hầu như luôn cho kết quả đúng ý. Mọi rắc rối nằm ở luật ba (thu hẹp) và một góc khuất của luật một (số âm gặp số không dấu) - Modern C dành riêng mục Implicit conversions để cảnh báo - bài này nối tiếp đúng tinh thần ấy.

hien.c - ba phép tính, hai luật

#include <stdio.h>

int main(void) {
    printf("%d\n", 7 / 2);       /* int / int      -> chia nguyen */
    printf("%.1f\n", 7 / 2.0);   /* int -> double  -> chia thuc   */
    printf("%d\n", 'A' + 1);     /* char -> int    -> 66          */
    return 0;
}

Kết quả khi chạy

3
3.5
66
  • Dòng 2: chỉ cần MỘT vế là double, cả phép toán thành double - đây là lý do "7 / 2.0 ra 3.5".
  • Dòng 3: 'A' là char mang mã 65; trước khi cộng, nó được THĂNG CẤP lên int → 65 + 1 = 66.
  • Hệ quả: cộng hai char vẫn ra int - C không tính toán trực tiếp ở cỡ char.

Phép gán ép giá trị về kiểu của đích. Đích hẹp hơn thì mất mát xảy ra im lặng:

thu-hep.c - bộ cờ -Wall -Wextra cảnh báo cả hai dòng gán này

#include <stdio.h>

int main(void) {
    int x = 3.9;            /* double -> int: CAT phan le */
    unsigned char c = 300;  /* 300 vuot 255 -> modulo 256 */
    printf("%d\n", x);
    printf("%d\n", c);
    return 0;
}

Kết quả khi chạy

3
44

Và đây là bẫy đáng sợ nhất của chuyển đổi ngầm định - số âm gặp số không dấu:

am-vs-khong-dau.c - vẫn bộ cờ đó, cảnh báo -Wsign-compare ở dòng so sánh

#include <stdio.h>

int main(void) {
    unsigned int u = -1;
    printf("%u\n", u);           /* -1 thanh bao nhieu? */
    printf("%d\n", -1 < 1u);     /* so sanh nguoc doi   */
    return 0;
}

Kết quả khi chạy

4294967295
0
  • 3.9 → 3: đổi số thực sang int là CẮT về phía 0, không làm tròn.
  • 300 → 44: kiểu không dấu quấn vòng theo modulo (300 − 256) - được chuẩn định nghĩa rõ.
  • -1 < 1u ra 0: int và unsigned int cùng hạng → vế CÓ DẤU bị đổi sang KHÔNG DẤU, -1 thành 4294967295.

Trung thực

Con số 4294967295 đúng cho unsigned int 32-bit - chuẩn C không ấn định cỡ int, nhưng máy tính cá nhân hiện nay đều dùng int 4 byte (Bài 5 đã nói "thường 4 byte" là vì vậy). Còn tràn số CÓ DẤU thì khác hẳn chuyện modulo ở trên: đó là undefined behavior - bài Bẫy kinh điển & undefined behavior của khoá C nâng cao mổ xẻ riêng.

Sáu ví dụ - đoán kết quả trước rồi bấm qua từng bước chuyển đổi. Cuối công cụ có máy trộn kiểu: chọn hai kiểu bất kỳ xem kiểu kết quả và lý do:

printf("%d", 7 / 2);
  1. 1 Cả 7 và 2 đều là int - không phải đổi gì.
bước 1/2

Máy trộn kiểu: a ⊕ b ra kiểu gì?

double

4 byte, có dấu · 8 byte, số thực

  • - Có một vế là double → vế còn lại được đổi sang double.

Không ai rà tay từng phép gán. Người viết C có nghề bật cảnh báo rồi để trình biên dịch chỉ điểm:

  • Bộ cờ của khoá (-std=c17 -Wall -Wextra) đã bắt được: gán làm đổi giá trị (3.9 → 3, 300 → 44) và so sánh signed/unsigned.
  • Thêm -Wconversion khi muốn soi gắt hơn: báo MỌI chuyển đổi ngầm định có thể mất mát (trên cùng file demo: 3 cảnh báo thành 5).
  • Quy tắc vàng: thấy cảnh báo chuyển đổi thì sửa KIỂU cho khớp, đừng ép kiểu (cast) cho im - cast là tắt radar đúng chỗ nguy hiểm.

Bài tiếp theo

Biểu thức - kể cả trộn kiểu - giờ trong tay mèo con rồi. Bài kế dùng chúng để chương trình biết ra quyết định: rẽ nhánh với if/elseswitch.

Câu hỏi thường gặp

Triết lý của C: tin lập trình viên và ưu tiên tiện + nhanh. Trộn int với double là việc quá thường gặp, bắt ép kiểu tay từng chỗ thì code rườm rà. Đổi lại, vài ca chuyển đổi âm thầm gây bất ngờ - nên bài này tồn tại, và nên bật cảnh báo để máy canh giùm.

Khi hai vế khác kiểu, C đổi vế "hẹp" sang kiểu "rộng" hơn để không mất thông tin TRONG PHÉP TÍNH: int gặp double thì lên double, int gặp long thì lên long. Tên đầy đủ trong chuẩn là "usual arithmetic conversions". Lưu ý nó chỉ áp dụng trong biểu thức - phép GÁN thì ngược lại: ép về kiểu của đích, kể cả khi phải cắt bớt.

CPU tính toán thuận nhất ở cỡ int, nên C quy định: charshort luôn được nâng lên int trước khi tham gia phép toán. Hệ quả thú vị: cộng hai char ra int, và một biểu thức toàn char vẫn không bao giờ tràn kiểu char giữa chừng.

Chuyển đổi ngầm định luôn CẮT về phía 0 (3.9 → 3, -3.9 → -3), không làm tròn. Muốn tròn thật thì dùng hàm round() trong <math.h> rồi mới gán. Phân biệt hai việc này tránh được cả lớp bug tính tiền, tính điểm.

Số âm lưu theo bù hai (đã gặp ở khoá "Máy tính hoạt động thế nào?"): -1 là chuỗi bit toàn số 1. Đem chuỗi bit đó đọc theo kiểu KHÔNG DẤU 32-bit thì ra 2³² − 1 = 4294967295. Chuyển đổi signed → unsigned không đổi bit nào cả - chỉ đổi cách ĐỌC.

Sớm hơn bạn nghĩ: sizeof và độ dài chuỗi/mảng trong C mang kiểu size_t - một kiểu KHÔNG DẤU. Viết for (int i = n - 1; i >= 0; …) với n kiểu size_t, hay so sánh chỉ số int với sizeof, là đụng ngay. Bài Mảng & chuỗi và khoá nâng cao sẽ nhắc lại đúng chỗ này.

Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.

Tick những điều em tự tin làm được sau khi học bài này. 0/6

Trả lời vài câu để chắc rằng em đã nắm bài.

Câu 1/3 Điểm: 0

Biểu thức 'A' + 1 trong C cho kết quả kiểu gì, giá trị bao nhiêu?

Bài tập về nhà

  1. 1

    Đoán hết 6 ví dụ

    Quay lại công cụ Bước 4: với mỗi ví dụ, tự đoán kết quả TRƯỚC khi bấm qua các bước.

    ✅ Hoàn thành khi: Đoán đúng 6/6 (hoặc giải thích lại được những ví dụ đoán sai mà không nhìn đáp án).

  2. 2

    Máy trộn kiểu trong đầu

    Không nhìn công cụ: charchar, intunsigned int, longdouble ra kiểu gì? Rồi kiểm bằng máy trộn ở cuối công cụ.

    ✅ Hoàn thành khi: int (thăng cấp cả hai) · unsigned int (vế có dấu bị đổi) · double (rộng hơn thắng).

  3. 3

    Tự chạy bẫy số âm

    Chép chương trình Bước 3 (u = -1) về máy, biên dịch bằng bộ cờ của khoá và chạy.

    ✅ Hoàn thành khi: Thấy 4294967295 và 0; trình biên dịch kèm cảnh báo sign-compare ngay dòng so sánh.

  4. 4

    Bật thêm radar

    Biên dịch lại chương trình Bước 2 + Bước 3 với cc -std=c17 -Wall -Wextra -Wconversion. Đếm số cảnh báo so với khi chưa có -Wconversion.

    ✅ Hoàn thành khi: Nhiều cảnh báo hơn hẳn (file demo của bài: 3 → 5) - -Wconversion bắt thêm các chuyển đổi có thể mất mát.

  5. 5

    Cắt hay tròn?

    Viết chương trình in (int)3.9, (int)-3.9, và round(3.9) (nhớ #include <math.h>, link -lm nếu Linux).

    ✅ Hoàn thành khi: 3, -3 (cắt về phía 0) và 4 (tròn thật) - nói được khi nào dùng cách nào.

  6. 6

    Săn bẫy trong code cũ

    Mở lại bài tập của các bài trước, tìm một chỗ trộn int với double hoặc so sánh hai kiểu khác nhau, và tự giải thích chuyển đổi nào đang xảy ra.

    ✅ Hoàn thành khi: Chỉ ra đúng vế nào bị đổi sang kiểu nào, đối chiếu được bằng máy trộn kiểu.