Sunday, March 27, 2016

Bài toán hay về: Chuyển đổi kiểu trong lập trình

Bài toán được đặt ra như sau:

Code Examples

12. What does the following code output and why?
void foo(void)
{
unsigned int a = 6;
int b = -20;
(a+b > 6) ? puts(“> 6”) : puts(“<= 6”);
}

-> This question tests whether you understand the integer promotion rules in C- an area that i find is very poorly understood by many developers. 
Anyway, the answer is that this outputs "> 6".
The reason for this is that expression involving signed  unsigned types have all operands promoted to unsigned types. Thus -20 becomes a very large positive integer and the expression evaluates to greater than 6 (caused -20 is converted from signed integer to unsigned integer).
This is a very important point in embedded systems where unsigned data types should be used frequently.

IF YOU GET THIS ONE WRONG, THEN YOU ARE PERILOUSLY CLOSE TO NOT BEING HIRED.
hahaha =)). xem thêm về thứ tự ưu tiên chuyển đổi bên dưới.

/*------------------------------------------------------------------------------------------------------

vậy Integer Promotion là gì?

.Numeric promotion


Bất cứ khi nào một giá trị từ một kiểu được chuyển đến giá trị của kiểu tương tự nhưng rộng hơn, nó được gọi là numeric promotion (hay là widening , nó thường xuyên dành cho kiểu int). Cho ví dụ, kiểu int có thể chuyển đến kiểu rộng hơn là long, float chuyển đến double.

long l(64); // mở rộng số nguyên 64 trong long
double d(0.12f); // "thăng cấp" float 0.12 trong kiểu double

Trong thuật ngữ "numeric promotion" chuyển một vài loại hình promotion,cũng có hai kiểu chuyển đổi khác cần lưu ý :
+ Intergral promotion liên quan đến chuyển đổi kiểu đổi của integer đến kiểu có phạm vi hẹp hơn int (ví dụ như bool, char, usigned char, signed char, usigned short , signed short) đến kiểu int (nếu có thể) hoặc kiểu unsigned int.

+Float point promotion liên quan đến chuyển đổi từ kiểu float sang double.


Hai loại trên đều được sử dụng trong các trườn hợp cụ thể đặc biệt để chuyển một kiểu dữ liệu nhờ nhơn đến int/ usigned int hay double, bởi vì thực hiện trên kiểu dữ liệu đó có hiệu suất nhất.

Một thứ quan trọng cần nhớ về promotions là chúng luôn an toán và không có dữ liệu nào bị mất.

.Phỏng đoán một biểu thức


Khi tính toán một biểu thức với các kiểu khác nhau, compiler tách từng biểu thức ra làm biểu thức con. Các toán tử toán học yêu cầu toán hạng của nó cùng kiểu. Nếu các toán hạng không cùng kiểu, compiler sẽ thực hiện chuyển đổi ngầm để chuyển một toán hạng. Để làm được điều này, nó phải tuân thủ các luật lệ sau:

+ NẾu toán hạng là kiểu nguyên, nó trải qua sự chuyển đổi "integral promotion" như đã nói ở trên.

+ Nếu toán hạng vẫn không phù hợp, compiler tìm toán hạng ưu tiên nhất (trong các toán hạng có trong biểu thức đó) và chuyển nó sang để phù hợp.

Toán hạng được ưu tiên như sau :

 + long double (highest)
 + double
 +float
 + unsigned long long
 + long long
 +unsigned long
 + long
 + usigned int
 + int (lowest)

/*-----------------------------------------------------------------------------------------*/

Hệ thống cấp bậc này có thể dẫn tới một vài vấn đề thú vị. Ví dụ, xem code :
std::cout << 5u - 10; // 5u có nghĩa là 5 như kiểu unsigned intteger

Bạn có lẽ mong rằng biểu thức 5u - 10 được tính là -5 vì 5 - 10 = -5 . Nhưng thực tế kết quả là
4294967291

Trong trường hợp này, signed integer (10) được "nâng cấp" lên thành unsigned int( vì nó có thự tự ưu tiên cao hơn), và biểu thức được tính với unsigned integer. Kết quả overflow  và chúng ta nhận kết quả ko như mong đợi

Đây là một trong rất nhiều lý do để tránh việc sử dụng unsigned int.

Cảm ơn các bạn đã dành thời gian để xem .

Source:
http://theironns.blogspot.com/2015/12/chuyen-oi-kieu-trong-lap-trinh-type.html





Posted By Thang Vuong 17:18

Little Endian Và Big Endian

Giới thiệu

Cũng như con người, máy tính cũng "nói" các ngôn ngữ khác nhau. Một số “đọc” và “ghi” từ trái sang phải, trong khi một số khác thì ngược lại. Một bộ máy có thể đọc và xử lý dữ liệu được tạo ra bởi nó một cách bình thường. Vấn đề xảy ra khi một bộ máy “khác loại” cố gắng đọc dữ liệu đó. Thuật ngữ “big endian” và “little endian” diễn tả sự khác nhau về cách đọc và ghi dữ liệu giữa các nền tảng máy tính. Bài viết sẽ cung cấp cho các bạn một số kiến thức về big và little endian.

Đối tượng hướng đến

Bài viết nói về cách lưu trữ bên dưới của máy tính, do đó nó dành cho các lập trình viên LOW LEVEL, nếu bạn có định hướng là lập trình viên ở mức HIGH LEVELvui lòng đọc ở mức độ xem như tham khảo.

Tổng quan

Endian là cách tổ chức dữ liệu trên một nền tảng máy tính. Bộ nhớ máy tính có thể được xem như một mảng có kích thước lớn, chia làm nhiều ô, mỗi ô có kích thước 1 byte. Nếu dữ liệu của bạn có thể được gói gọn trong 1 byte, nghĩa là mỗi lần bạn chỉ xử lý 1 byte thì không có gì khác biệt.
Vấn đề xảy ra khi dữ liệu của bạn vượt quá 1 byte, ví dụ như bạn cần lưu trữ một số nguyên. Khi đó 4 byte của biến số nguyên đó sẽ được lưu vào bộ nhớ máy tính theo thứ tự nào?

Big Endian

Đối với các nền tảng dùng big endian, Least Significant bit (LSB) luôn được lưu ở ô nhớ có địa chỉ lớn nhất còn Most Significant Bit (MSB) được lưu ở ô nhớ có địa chỉ nhỏ nhất trong vùng lưu trữ của biến. Trong đó, LSB là bit có trọng số nhỏ nhất, nằm ở ngoài cùng bên phải; MSB là bit có trọng số lớn nhất, nằm ở ngoài cùng bên trái của một biến.

Big Endian
Lưu trữ số nguyên 7411 vào bộ nhớ máy trên nền tảng dùng big endian. Ta cùng xét ví dụ sau:
Số 7411 được viết dưới dạng nhị phân như sau:
1 1100 1111 0011
Minh họa việc lưu số 7411 vào bộ nhớ máy tính như sau:
Big Endian

Note: Viết như thế nào thì nó thể hiện trong Big Endian như thế đó, với trình tự bit cao tới bit thấp là từ trái qua phải và địa chỉ cũng tăng theo chiều đó.

Giới thiệu

Cũng như con người, máy tính cũng "nói" các ngôn ngữ khác nhau. Một số “đọc” và “ghi” từ trái sang phải, trong khi một số khác thì ngược lại. Một bộ máy có thể đọc và xử lý dữ liệu được tạo ra bởi nó một cách bình thường. Vấn đề xảy ra khi một bộ máy “khác loại” cố gắng đọc dữ liệu đó. Thuật ngữ “big endian” và “little endian” diễn tả sự khác nhau về cách đọc và ghi dữ liệu giữa các nền tảng máy tính. Bài viết sẽ cung cấp cho các bạn một số kiến thức về big và little endian.

Tiền đề bài viết

Trong quá trình nghiên cứu, tôi đã bắt gặp hai thuật ngữ này và đã có sự đầu tư tìm hiểu. Được anh Kevin La :: www.stdio.vn/users/index/1/kevin-la động viên, tôi đã tổng hợp lại kiến thức và chia sẻ với các bạn trong bài viết này.

Đối tượng hướng đến

Bài viết nói về cách lưu trữ bên dưới của máy tính, do đó nó dành cho các lập trình viên LOW LEVEL, nếu bạn có định hướng là lập trình viên ở mức HIGH LEVELvui lòng đọc ở mức độ xem như tham khảo.

Tổng quan

Endian là cách tổ chức dữ liệu trên một nền tảng máy tính. Bộ nhớ máy tính có thể được xem như một mảng có kích thước lớn, chia làm nhiều ô, mỗi ô có kích thước 1 byte. Nếu dữ liệu của bạn có thể được gói gọn trong 1 byte, nghĩa là mỗi lần bạn chỉ xử lý 1 byte thì không có gì khác biệt.
Vấn đề xảy ra khi dữ liệu của bạn vượt quá 1 byte, ví dụ như bạn cần lưu trữ một số nguyên. Khi đó 4 byte của biến số nguyên đó sẽ được lưu vào bộ nhớ máy tính theo thứ tự nào?

Big Endian

Đối với các nền tảng dùng big endian, Least Significant bit (LSB) luôn được lưu ở ô nhớ có địa chỉ lớn nhất còn Most Significant Bit (MSB) được lưu ở ô nhớ có địa chỉ nhỏ nhất trong vùng lưu trữ của biến. Trong đó, LSB là bit có trọng số nhỏ nhất, nằm ở ngoài cùng bên phải; MSB là bit có trọng số lớn nhất, nằm ở ngoài cùng bên trái của một biến.

Big Endian
Lưu trữ số nguyên 7411 vào bộ nhớ máy trên nền tảng dùng big endian. Ta cùng xét ví dụ sau:
Số 7411 được viết dưới dạng nhị phân như sau:
1 1100 1111 0011
Minh họa việc lưu số 7411 vào bộ nhớ máy tính như sau:
Big Endian

Các nền tảng sử dụng Big Endian

IBM zSeries, iSeries, AIX, NonStop Kernel, Motorola 68K…

Little Endian

Ngược lại với các nền tảng sử dụng big endian, ở các nền tảng sử dụng little endian, LSB luôn được lưu ở ô nhớ có địa chỉ nhỏ nhất còn MSB được lưu ở ô nhớ có địa chỉ lớn nhất trong vùng lưu trữ của biến.
Xét lại ví dụ trên, việc lưu trữ số nguyên 7411 vào bộ nhớ máy tính được minh họa như sau:

cứ bốc 8 bit như trên và bỏ vào theo như bên dưới, ngược lại với cách viết thông thường. nó đi từ phải quá trái với giá trị thấp tại bit có trọng số cao trước.

Little

Các nền tảng sử dụng Little Endian

Hầu hết các hệ thống sử dụng vi xử lý của Intel.
























Posted By Thang Vuong 16:44

Ý Nghĩa Của Từ Khóa Static Trong C

MỞ ĐẦU:

Khi học C cơ bản, chắc hẳn bạn sẽ gặp cách dùng từ khóa static như ví dụ dưới đây.


file1.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

void count(int i)
{
    static int num = 0;
    num += i;
    printf("current value of num: %d\n", num);
}

int main()
{
    count(1);
    count(3);
    return 0;
}
Kết quả khi chạy chương trình sẽ là:

file2.sh
1
2
current value of num: 1
current value of num: 4

Biến num khai báo static như trên có 2 đặc điểm:
1. Do được khai báo static nên chỉ được khởi tạo 1 lần duy nhất và tồn tại suốt thời gian chạy của chương trình. Gía trị của biến count sẽ được tích lũy mỗi khi hàm count được gọi.
2. Do khai báo trong nội bộ hàm count nên biến chỉ có thể được nhìn thấy/truy xuất bởi các câu lệnh trong hàm count. Nói cách khác, biến num là  biến nội bộ (local variable).

Tuy vậy bạn sẽ bất ngờ khi bắt gặp những cách sử dung static trong như ví dụ dưới đây:

main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>

static int a = 0;

static void count(int i)
{
    static int num = 0;
    num += i;
    printf("current value of num: %d\n", num);
}

int main()
{
    a += 1;
    printf("value of a: %d\n", a);
    count(1);
    count(3);
    return 0;
}
ta bắt gặp static ở 2 nơi nữa
1. Static trong khai báo hàm
2. Static trong khai báo biến toàn cục

2 từ khoá static này có ngữ nghĩa như thế nào?

Để hiểu được ngữ nghĩa mới của static này, ta cần hiểu 1 khái niệm: đơn vị biên dịch (translation unit).
Mỗi project thường được viết trên nhiều file (vì mục đích phân chia module, đảm bảo tính dễ bảo trì). Mỗi file.c trong dự án sẽ là 1 đơn vị biên dịch. Quá trình biên dịch 1 project C sẽ là: biên dịch các đơn vị độc lập .c ra các object file .o (*.obj) và liên kết (link) các đơn vị object file thành chương trình.
Mỗi đơn vị sẽ có các thủ tục (procedure) hoặc function riêng. Code ở 1 đơn vị biên dịch có thể sử dụng thủ tục hoặc hàm, hay cả biến toàn cục ở đơn vị biên dịch khác. Ví dụ:

main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//-----------------------
//A.c

int avar;

void a() {};

void b() {};

// -----------------------
//C.c

extern int avar;

void c() {};

void d() {};
thì trong a() của A.c ta có thể gọi c() một cách thoải mái. Biến avar sẽ được sử dụng cả ở A.c và C.c (biến toàn cục thực thụ!)
Để hạn chế việc sử dụng này (tránh va đụng tên hàm giữa các đơn vị biên dịch), người ta đưa khái niệm hàm tĩnh (static function) và biến tĩnh (static global variable).
Ngữ nghĩa:
  • Biến toàn cục tĩnh sẽ có phạm vi trên đơn vị biên dịch. Điều đó có nghĩa là đơn vị khác không có cách nào truy cập được biến này.
  • Hàm tĩnh sẽ có phạm vi trên đơn vị biên dịch. Điều đó có nghĩa đơn vị khác không thể truy cập được hàm này.
Do đó

main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//-----------------
//A.c

static int avar;

static void a() {};

void b() {};

//------------------
//C.c

extern int avar;

void c() {};

void d() {};
Nếu ta khai báo static như trên, các hàm c, d trong C.c sẽ không thể nào truy cập được hàm a cũng như biến avar (dù rằng avar được khai báo extern trong C.c).

TÓM LƯỢC:
static có 2 ngữ nghĩa chính:
  • Khi được sử dụng trong phạm toàn cục của 1 đơn vị biên dịch, static hạn chế truy cập từ các đơn vị biên dịch khác (file .c khác) (áp dụng với cả hàm và biến toàn cục).
  • Khi được sử dụng trong phạm vi cục bộ của 1 thủ tục hay hàm, static có nghĩa là biến được khai báo tồn tại trong suốt thời gian chạy cảu chương trình và chỉ được khởi tạo 1 lần duy nhất.

Posted By Thang Vuong 15:57

Ý Nghĩa Của Từ Khóa Volatile Trong C

Mở đầu

Trong lập trình nhúng (embedded system), ta rất thường hay gặp khai báo biến với từ khóa volatile. Việc khai báo biến volatile là rất cần thiết để tránh những lỗi sai khó phát hiện do tính năng optimization của compiler. Trong bài viết này, ta sẽ tìm hiểu ý nghĩa của từ khóa này, cách sử dụng nó và giải thích tại sao nó quan trọng trong một số trường hợp lập trình với hệ thống nhúng và lập trình ứng dụng đa luồng.

Tại sao cần phải có biến volatile

Cách khai báo biến với từ khóa volatile:


1
2
3
4
5
volatile int foo;//both this way...
int volatile foo;//... and this way is OK! Define a volatile integer variable

volatile uint8_t *pReg;//both this way...
uint8_t volatile *pReg;//... and this way is OK! Define a pointer to a volatile unsigned 8-bit integer
Một biến cần được khai báo dưới dạng biến volatile khi nào? Khi mà giá trị của nó có thể thay đổi một cách không báo trước. Trong thực tế, có 3 loại biến mà giá trị có thể bị thay đổi như vậy:
  • Memory-mapped peripheral registers (thanh ghi ngoại vi có ánh xạ đến ô nhớ)
  • Biến toàn cục được truy xuất từ các tiến trình con xử lý ngắt (global variables modified by an interrupt service routine)
  • Biến toàn cục được truy xuất từ nhiều tác vụ trong một ứng dụng đa luồng (global variables accessed by multiple tasks within a multi-threaded application).

Thanh ghi ngoại vi

Trong các hệ thống nhúng thường có các thiết bị ngoại vi (ví dụ như cổng vào ra đa chức năng GPIO, cổng UART, cổng SPI, …), và các thiết bị ngoại vi này chứa các thanh ghi mà giá trị của nó có thể thay đổi ngoài ý muốn của dòng chương trình (program flow). Ví dụ một thanh ghi trạng thái pStatReg, ta cần phải thực hiện polling thanh ghi trạng thái này đến khi nó khác 0 (Đoạn code minh họa với Keil ARM compiler, trên vi điều khiển ARM LPC2368)


mappedIOi_nonvolatile.c
1
2
3
unsigned long * pStatReg = (unsigned long*) 0xE002C004;
//Wait for the status register to become non-zero
while(*pStatReg == 0) { }
Đoạn code này có gì không ổn? Nó sẽ chạy sai khi ta bật chức năng tối ưu (optimization) của compiler. Quan sát mã assembly mà compiler xuất ra của đoạn code trên như sau:


mappedIO_nonvolatile.s
1
2
3
4
5
6
7
8
9
10
        LDR      r0,|L2.564|
        SUB      sp,sp,#0x10
        LDR      r0,[r0,#0]
|L2.22|
        CMP      r0,#0
        BEQ      |L2.22|
        LDR      r1,|L2.564|
...
|L2.564|
        DCD      0xe002c004
Trước khi vào label |L2.22|, tương ứng với vòng lặp while, thanh ghi r0 được ghi vào giá trị được lưu trong ô nhớ 0xE002C004. Khi vào vòng lặp while, compiler thực hiện ngay việc so sánh giá trị của thanh ghi r0 với 0. Tại sao lại như vậy? Vì compiler nhận thấy biến pStatReg là một biến normal, giá trị của nó được hiểu là không thể thay đổi một cách bất thường. Do vậy, khi bật tối ưu, compiler sẽ chỉ thực hiện so sánh giá trị của r0 mà không load lại giá trị này từ ô nhớ 0xE002C004, vì theo flow của chương trình thì biến pStatReg không bị thay đổi ở bất cứ đâu. Do đó, vòng lặp while sẽ chạy vô tận, hoặc không chạy gì cả (tùy theo giá trị ban đầu mà pStatReg trỏ đến).
Điều gì sẽ xảy ra nếu ta đổi biến pStatReg sang volatile?


mappedIO_volatile.c
1
2
3
volatile unsigned long * pStatReg = (unsigned long*) 0xE002C004;
//Wait for the status register to become non-zero
while(*pStatReg == 0) { }
Mã assembly mà compiler xuất ra sẽ như sau:


mappedIO_volatile.s
1
2
3
4
5
6
7
8
9
10
    SUB      sp,sp,#0x10
        LDR      r0,|L3.544|
|L3.4|
        LDR      r1,[r0,#0]
        CMP      r1,#0
        BEQ      |L3.4|
        LDR      r1,|L3.544|
...
|L3.544|
        DCD      0xe002c004
Điều gì khác biệt ở đây? Đầu tiên, ở dòng LDR trước label |L3.4|, compiler đã đặt địa chỉ 0xE002C004 vào thanh ghi r0. Ở dòng LDR đầu tiên ngay sau label |L3.4|, ta thấy compiler đã LOAD LẠI GIÁ TRỊ của ô nhớ 0xE002C004 vào ô nhớ r1! Sau đó nó mới thực hiện so sánh giá trị của ô nhớ r1 này với 0.
Lý do là gì? Vì ta đã đặt biến pStatReg là biến volatile, để báo hiệu là biến này có thể thay đổi một cách bất thường, ngoài flow của chương trình. Do vậy nên, để “đề phòng”, compiler lúc nào cũng phải load lại giá trị mới của ô nhớ 0xE002C004, để đảm bảo mình có giá trị mới nhất!

Đến đây, bạn có thể hỏi là “giá trị bị thay đổi một cách bất thường” là như thế nào? Hiện tượng này đặc biệt hay xảy ra khi lập trình nhúng. Trong hệ thống nhúng, một thanh ghi có thể bị thay đổi giá trị do những điều kiện bên ngoài. Ví dụ như mức điện áp không vượt quá ngưỡng, làm cho giá trị 0 thành 1, 1 thành 0. Hoặc, khi cổng UART nhận được đầy buffer thì thanh ghi BUFFER_READY tự động chuyển 0 thành 1… Bằng cách sử dụng biến volatile, chương trình C được compiler biên dịch sẽ đảm bảo luôn luôn đọc lại giá trị của thanh ghi, tránh mọi assumption của compiler.

Tiến trình con xử lý ngắt (Interrupt Service Routine)

Ngắt là một khái niệm quan trọng trong hệ thống nhúng. Có nhiều loại ngắt khác nhau như ngắt vào ra (I/O), ngắt SPI, ngắt UART… Mỗi khi xảy ra ngắt, stack pointer sẽ nhảy đến chương trình con xử lý ngắt (ISR). Thường thì các chương trình con xử lý ngắt này sẽ thay đổi giá trị của biến toàn cục và trong chương trình chính sẽ đọc những giá trị này để xử lý.
Để dễ hiểu, lấy ví dụ ngắt cổng serial (UART) kiểm tra các kí tự nhận được xem có phải là 0xFF không. Nếu là kí tự 0xFF, ISR sẽ set một biến cờ toàn cục. Nếu không có volatile, code như sau:


isr.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int etx_rcvd = FALSE;

void main()
{
  ...
  while(!ext_rcvd)
  {
      //Wait...
  }
  ...
}

interrupt void rx_isr(void)
{
  ...
  if(0xFF == rx_char)
  {
      ...
      etx_rcvd = TRUE;
  }
  ...
}
Ta để ý trong main(): compiler không thể biết được là biến ext_rcvd có thể bị thay đổi trong ISR. Compiler dò đoạn code, thấy rằng biểu thức !ext_rcvd luôn đúng, vì thế không thể thoát được vòng lặp while. Nếu compiler được bật optimization lên, tất cả đoạn code sau vòng lặp while sẽ bị loại bỏ. Nếu không có warning cẩn thận, chương trình của ta có thể bị lỗi mà phát hiện rất khó.
Giải pháp là đặt biến ext_rcvd là biến volatile. compiler sẽ biết đó là biến có thể bị thay đổi theo một cách nào đó ngoài ý muốn (ở đây là do ISR). Compiler sẽ bị buộc phải check giá trị của biến ext_rcvd.

Ứng dụng đa luồng

Trong các ứng dụng đa luồng, thường xảy ra trường hợp các tác vụ trao đổi thông tin với nhau thông qua một biến toàn cục. Như vậy, một tác vụ thay đổi giá trị của biến toàn cục cũng sẽ giống như trường hợp ISR ở trên. Nếu compiler mà bật tính năng optimization thì sẽ xảy ra vấn đề.
Ví dụ đoạn code


multithreaded.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int cntr;

void task1(void)
{
  cntr = 0;
  
  while(cntr == 0)
  {
      sleep(1);
  }
  //code follows...
}

void task2(void)
{
  //...code...
  cntr ++;
  sleep(10);
  //...code...
}
Cách khắc phục vấn như cũ: đặt biến cntr thành biến volatile !
/*-----------------------------------------------------------------------------------------------------------------------------*/
2) Can a variable be both const and volatile?
The const keyword make sure that the value of the variable declared as const can't be changed. This statement holds true in the scope of the program. The value can still be changed by outside intervention. So, the use of const with volatile keyword makes perfect sense.

Posted By Thang Vuong 15:18