🌱 Embedded C - Bàn Về Variable/Biến và Những Góc Nhìn Thú Vị
Trong lập trình, đặc biệt là với ngôn ngữ C, biến đóng vai trò cốt lõi để lưu trữ và xử lý dữ liệu. Chẳng hạn, khi cần lưu trữ giá trị nhiệt độ để hiển thị lên màn hình khi cần, lưu trữ giá trị tính toán, ... Hiểu rõ cách sử dụng biến không chỉ giúp viết code hiệu quả mà còn tối ưu hóa tài nguyên trên các thiết bị nhúng có bộ nhớ và sức mạnh xử lý hạn chế.
Bài viết này sẽ bàn về biến (variable) trong ngôn ngữ lập trình C, cũng như một số khái niệm liên quan như Identifier, Constants and Literals.
Mục Lục
C Variable - Chi tiết về Biến
Variable - biến, trong toán học được hiểu là một đối tượng có tên gọi, mục đích là giữ một giá trị nào đó, nhằm mục đích tính toán, chắc hẳn bạn chưa quên bài toán Tìm X hồi trung học! Đối với lập trình, Variable cũng có tác dụng tương tự nhưng mở rộng hơn, mục đích là lưu trữ dữ liệu + dùng để tính toán, truyền thông, ...
Giống như biến trong toán học được ghi nhớ bởi con người, variable trong lập trình cần được lưu trữ bởi máy tính, trên bộ nhớ (thường là RAM).
Một số đặc điểm quan trọng của variable:
- Identifier - Variable là một đối tượng có tên, và để tránh trùng lặp, trong một scope (ví dụ trong một chương trình, hoặc một hàm), tên biến phải là duy nhất - từ đó ta có khái niệm Identifier (định danh) là tên đặt cho các biến, cũng như một số quy tắc đặt tên.
- Memory Location - Variable sẽ được lưu trữ trên bộ nhớ, vì vậy nó sẽ chiếm một số lượng bộ nhớ nhất định (đơn vị byte), và nằm trên một số ô nhớ. Vì vậy, biến sẽ có địa chỉ (sẽ nói rõ hơn ở bài Pointer).
- Value - Variable lưu trên các ô nhớ nào đó, vậy ngay cả khi bạn không gán bất kỳ giá trị nào cho nó, nó vẫn có giá trị (chính là giá trị các ô nhớ đại diện cho biến đó). Việc sử dụng phép gán giá trị cho biến, thực tế là thay đổi giá trị các ô nhớ đại diện cho biến đó.
- Data Type - Variable lưu trên bộ nhớ, vậy nó lưu mấy byte? Lúc CPU đọc ra thì nó là số nguyên hay số thực? Điều đó được quyết định bởi khái niệm Data Type - Kiểu dữ liệu của biến. Khi bạn tạo ra một biến, nó luôn cần có Data Type.
Cú pháp khai báo biến
Để tạo ra một biến, chúng ta có thể sử dụng cú pháp sau:
int var; // Declaration
int c = 10; // Definition
↪ Declaration : Dòng code đầu tiên là khai báo biến với tên là var, với kiểu dữ liệu là int (số nguyên), cách làm này gọi là Declaration - tức chỉ đơn giản là báo cho compiler biết biến này tồn tại. Với dòng này, biến var sẽ được tạo ra - tức là một vùng nhớ được tạo ra trên bộ nhớ, và có định danh là var.
Ở đây không nhắc đến giá trị của var, nên giá trị của nó sẽ phụ thuộc vào vị trí bộ nhớ chưa biến này.
↪ Definition : Dòng code thứ hai là khai báo biến c, kiểu dữ liệu tương tự var, nhưng được gán giá trị là 10. Tức là biến c này được tạo ra - một vùng nhớ được tạo ra trên bộ nhớ, và có định danh là c, có giá trị là 10.
Quy tắc đặt tên biến
Bạn có thể gán bất kỳ tên nào cho một biến C miễn là nó tuân theo các quy tắc sau:
- Tên biến chỉ được chứa các chữ cái, chữ số và dấu gạch dưới.
- Tên biến phải bắt đầu bằng một chữ cái hoặc dấu gạch dưới. Không được bắt đầu bằng một chữ số.
- Không được phép có khoảng trắng trong tên biến.
- Tên biến không được là bất kỳ từ hoặc từ khóa nào được dành riêng.
- Tên phải là duy nhất trong chương trình.
Một số ví dụ về cách đặt tên |
Scope - Phạm vi của các biến trong C
Trong C, một biến có thể được truy cập bằng tên của nó ở bất kỳ đâu trong một vùng cụ thể của chương trình được gọi là scope - phạm vi của nó. Đó là vùng của chương trình mà tên được gán cho biến là hợp lệ.
Ở trên mình có nói "Tên của biến là duy nhất", điều này chỉ đúng một phần, cần chỉnh lại là "Tên của biến là duy nhất trong một scope", ngoài scope đó, có thể có những biến khác trùng tên, compiler sẽ phân biệt được điều này.
Scope đơn giản nhất là vùng bên trong dấu ngoặc nhọn {}, mình sẽ lấy ví dụ sau:
- #include <stdio.h>
- int var = 7; // Global
- void main()
- {
- int var = 5; // Local
- {
- int var = 10; // Local - block
- }
- printf("var = %d", var);
- }
↪ Khi chạy đoạn code trên kết quả sẽ là var = 5.
Giải thích đơn giản: câu lệnh printf ở dòng 12 cùng scope với câu lệnh int var = 5 ở dòng số 7. Trong khi đó biến var ở dòng số 9, chỉ dùng trong block của nó được đặc trong 2 dấu ngoặc nhọn. Còn biến var ở dòng số 3, là biến global, được dùng cho toàn bộ chương trình. Phần Scope này mình sẽ nói rõ hơn ở bài Scope & Storage Class khi bạn đã học về Functions.
C Keywords
Keyword là những từ khóa, các lệnh được định nghĩa trước trong C với một mục đích cụ thể nào đó, về cơ bản thì biến tên/hàm tạo ra không thể trùng lặp với các Keyword này.
Vì C là ngôn ngữ phân biệt chữ hoa chữ thường, tất cả các Keyword phải được viết bằng chữ thường. Dưới đây là danh sách tất cả các từ khóa được phép trong ANSI C.
C Keywords | |||
---|---|---|---|
auto | double | int | struct |
break | else | long | switch |
case | enum | register | typedef |
char | extern | return | union |
continue | for | signed | void |
do | if | static | while |
default | goto | sizeof | volatile |
const | float | short | unsigned |
C Constant & Literal
C Constant
Ngoài các biến được đề cập như ở trên, C cũng cung cấp một số biến có giá trị không thể thay đổi. Các biến này được gọi là constant - hằng số và được tạo ra đơn giản bằng cách thêm tiền tố const vào từ khóa trong khai báo biến.
const data_type name = value;
Constant được sử dụng để lưu trữ các giá trị cố định, giúp code dễ đọc, dễ bảo trì và tránh lỗi khi sử dụng các giá trị cố định nhiều lần.
Về cơ bản thì một biến được khai báo là const có các đặc điểm giống như biến thông thường, từ scope, vị trí trên bộ nhớ, ... Chỉ có điểm khác duy nhất là không thể thay đổi nó bằng phép gán (=).
const float PI = 3.14;
PI = 3.16; // Compile failed
Nếu dùng phép gán (=) cho constant variable, compiler sẽ báo build fail.
Lưu ý: Bạn vẫn hoàn toàn có thể thay đổi biến const bằng những cách thức khác như tác động trực tiếp vào vùng nhớ chứa biến đó (chẳng hạn sử dụng pointer).
C Literals
Literal là dữ liệu được sử dụng để biểu diễn các giá trị cố định. Chúng có thể được sử dụng trực tiếp trong code. Ví dụ: 1, 2.5, 'c' v.v. Chẳng hạn lúc chúng ta gán var = 10, thì var là biến, còn 10 chính là Literal.
Với kiểu dữ liệu mà biến có thể có, Literal trong C cũng có một số loại sau:
➤ Integer Literals (Hằng số nguyên)
- Là các giá trị số nguyên được viết trực tiếp trong code, ví dụ: 42, -10, 0.
- Có thể biểu diễn ở các dạng:
- Cơ số 10 (Decimal): 123, -456.
- Cơ số 8 (Octal): Bắt đầu bằng 0, ví dụ: 0123 (tương đương 83 trong cơ số 10).
- Cơ số 16 (Hexadecimal): Bắt đầu bằng 0x hoặc 0X, ví dụ: 0x7B (tương đương 123 trong cơ số 10).
- Nhị phân (Binary): Bắt đầu bởi 0b, ví dụ 0b1011 (Được hỗ trợ bởi tùy compiler)
- Hậu tố để chỉ định kiểu:
- U hoặc u: unsigned int (123U).
- L hoặc l: long (123L).
- UL hoặc ul: unsigned long (123UL).
- LL hoặc ll: long long (123LL).
- ULL hoặc ull: unsigned long long (123ULL).
- Ví dụ:
int dec = 42; // Decimal
int oct = 052; // Octal (số 42 trong cơ số 10)
int hex = 0x2A; // Hexadecimal (số 42 trong cơ số 10)
int bin = 0b101010; // Binary (số 42 trong cơ số 10)
unsigned long ul = 123UL;
➤ Floating-Point Literals (Hằng số số thực)
- Là các giá trị số thực, ví dụ: 3.14, -0.001, 2.5e3 (tương đương 2500.0).
- Có thể được viết dưới dạng:
- Số thực thông thường: 3.14159, -0.5.
- Ký hiệu khoa học (exponential): 2.5e3 (2.5 × 10³), 1.23e-4 (1.23 × 10⁻⁴).
- Hậu tố để chỉ định kiểu:
- F hoặc f: float (3.14F).
- L hoặc l: long double (3.14L).
- Không có hậu tố: mặc định là double.
➤ Character Literals (Hằng số ký tự)
- Là một ký tự đơn được bao quanh bởi dấu nháy đơn ('), ví dụ: 'A', '1', '$'.
- Được lưu trữ dưới dạng mã ASCII (ví dụ: 'A' có giá trị 65) - Thực tế char vẫn có giá trị là số nguyên, chỉ là chúng ta nhìn nó được mã hóa dưới dạng mã ASCII - Ở đây, trên bộ nhớ vẫn lưu số 65 (binary: 0b1000001) - khi ta đọc dưới dạng số nguyên thì ra 65, đọc dưới dạng ký tự thì ra 'A'.
- Ký tự escape: Dùng để biểu diễn các ký tự đặc biệt:
- \n: Dòng mới.
- \t: Tab ngang.
- \\: Dấu gạch chéo ngược.
- \': Dấu nháy đơn.
- \": Dấu nháy kép.
- \0: Ký tự null (kết thúc chuỗi).
- \xhh: Ký tự theo mã hexadecimal (ví dụ: \x41 là 'A').
- \ooo: Ký tự theo mã octal (ví dụ: \101 là 'A').
char c1 = 'A'; // Ký tự A
char c2 = '\n'; // Dòng mới
char c3 = '\x41'; // Ký tự A (hexadecimal)
➤ String Literals (Hằng số chuỗi)
- Là chuỗi ký tự được bao quanh bởi dấu nháy kép ("), ví dụ: "Hello, World!".
- Luôn tự động thêm ký tự null (\0) ở cuối để đánh dấu kết thúc chuỗi.
- Có thể sử dụng ký tự escape tương tự như hằng số ký tự.
- Lưu ý: Các chuỗi literals giống nhau trong mã nguồn thường được lưu trữ ở cùng một vị trí bộ nhớ (trong read-only data segment) để tối ưu hóa.
- Ví dụ:
char *str = "Hello, World!"; char *str2 = "Hello\nWorld"; // Chuỗi với ký tự xuống dòng
➤ Boolean Literals
- Trong C chuẩn (C99 trở đi), hằng số boolean được định nghĩa trong <stdbool.h> với hai giá trị:
- true (giá trị 1).
- false (giá trị 0).
- Kiểu này thì mình không expect sử dụng lắm vì nó không đại diện cho toàn bộ các chuẩn C.
➤ Vị trí lưu trữ của literals
- Integer và Floating-Point Literals: Thường được nhúng trực tiếp vào mã máy (code segment) hoặc được trình biên dịch thay thế trong quá trình tối ưu hóa. Nếu được gán cho biến, giá trị sẽ được lưu trong stack, heap, hoặc data segment tùy thuộc vào ngữ cảnh.
- Character Literals: Được lưu dưới dạng giá trị số (ASCII) trong mã máy hoặc vùng dữ liệu.
- String Literals: Được lưu trong read-only data segment (phân đoạn dữ liệu chỉ đọc). Nếu cố gắng sửa đổi chuỗi literal (ví dụ: thông qua con trỏ), sẽ dẫn đến lỗi segmentation fault hoặc hành vi không xác định.
Kết luận: Trên đây là những đặc điểm về Variable trong C do mình tóm tắt, mình có nói sâu hơn một chút về phần bộ nhớ và scope của biến, vì những yếu tố này rất quan trọng trong Embeded. Vẫn còn có những kiến thức sâu hơn về Variable mà bạn cần nắm được, và sẽ được bàn luận ở các bài viết sau trong Series này, nếu có câu hỏi/góp ý hãy comment bên dưới nhé!
>>>>>> Follow ngay <<<<<<<
Để nhận được những bài học miễn phí mới nhất nhé 😊
Chúc các bạn học tập tốt 😊