MSIL cơ bản – Part 3/6: Rẽ nhánh và vòng lặp

CIL không hỗ trợ các cấu trúc điều kiện và vòng lặp như for, while,…. Thay vào đó bạn phải sử dụng thông qua các lệnh nhảy và nhãn. Thông thường rẽ nhánh được sử dụng kết hợp với việc kiểm tra điều kiện. Vòng lặp cũng được thực hiện theo cách tương tự rẽ nhánh.

Rẽ nhánh

Để gán nhãn cho một dòng lệnh, bạn viết tên nhãn ở đầu dòng và sau đó là dấu hai chấm “:”, tương tự như trong C++, C#,… sau đó dùng lệnh br, brtrue, brfalse để nhảy đến một nhãn với cú pháp:

br <tên nhãn>

brtrue <tên nhãn>

brfalse <tên nhãn>

Khác với br lệnh rẽ nhánh không cần điều kiện, hai lệnh rẽ nhánh brtruebrfalse sẽ kiểm tra giá trị trong stack và sẽ thực hiện việc nhảy đến nhãn nếu như đỉnh stack có giá trị 1 (đối với brtrue) hoặc 0 (đối với brfalse).

Để kiểm tra một điều kiện, bạn sử dụng các lệnh CIL tương ứng với từng trường hợp. Các lệnh kiểm tra điều kiện này sẽ lấy 2 giá trị từ đỉnh stack ra và tiến hành so sánh. Kết quả trả về của việc so sánh này là một kiểu int32 với giá trị 1 hoặc 0 sẽ được đưa lại vào stack. Giá trị 1 tương đương với true và 0 tương đương với false.

Giả sử đỉnh stack có hai giá trị là value1 và value2 (value2 được thêm vào sau value1). Các lệnh sau sẽ đưa 1 vào stack nếu value1 và value2 đúng với điều kiện, ngược lại sẽ đưa 0 vào stack.. Các lệnh dùng để so sánh trong CIL (bạn có thể xem tại CIL Instruction Set):

  • ceq:     (compare equals) value1 bằng value2
  • cgt:      (compare greater than) value1 > value2
  • clt:       (compare less than) value1 < value2

Ta thử làm một ví dụ về so sánh hai số và in ra kết quả ra màn hình.

Example 1.1:

.assembly extern mscorlib{}
.assembly Branching{}

.method static void main()
{
	.entrypoint
	.maxstack 2
	.locals init (int32,int32,int32)

	ldstr "Enter first number: "
	call void [mscorlib]System.Console::Write(string)
	call string [mscorlib]System.Console::ReadLine()
	call int32 [mscorlib]System.Int32::Parse(string)
	stloc.0

	ldstr "Enter second number: "
	call void [mscorlib]System.Console::Write(string)
	call string [mscorlib]System.Console::ReadLine()
	call int32 [mscorlib]System.Int32::Parse(string)
	stloc.1

	ldloc.0
	ldloc.1
	cgt
	brtrue greater
	ldstr "First number is less than or equal to the second number."
	br print
greater:
	ldstr "First number is greater than the second number."

print:
	call void [mscorlib]System.Console::WriteLine(string)
	ret
}

Phần đầu của chương trình này tương tự như trong bài về biến cục bộ lần trước tôi giới thiệu: yêu cầu nhập dữ liệu từ người dùng và lưu vào 2 biến. Tiếp theo các dòng lệnh sau:

ldloc.0
ldloc.1
cgt
brtrue greater

Hai lệnh ldloc có nhiệm vụ nạp 2 biến vào stack và lệnh cgt như đã mô tả ở trên, dùng để kiểm tra: nếu biến thứ nhất lớn hơn biến thứ 2 thì đưa 1 vào stack, ngược lại thì đưa 0 vào stack. Tiếp đến lệnh rẽ nhánh brtrue sẽ thực hiện kiểm tra stack và nhảy đến nhãn greater nếu đỉnh stack có giá trị là 1 (true). Với kinh nghiệm lập trình đã có, bạn có thể dễ dàng hiểu được phần lệnh còn lại có nhiệm vụ gì. Điểm mấu chốt của chương trình là sẽ nạp chuỗi nào nào vào stack trước khi phương thức WriteLine(string) được thực hiện.

Ở ví dụ trên, thay vì sử dụng hai lệnh so sánh và nhảy liên tiếp nhau, bạn có thể dùng một lệnh kết hợp để thực hiện cùng một lúc cả 2 nhiệm vụ này.Chúng sẽ thực hiện so sánh với hai giá trị ở đỉnh stack và thực hiện lệnh nhảy nếu:

  • beq:         Hai giá trị bằng nhau   (==)
  • bne:         Không bằng nhau (!=)
  • bge:         Lớn hơn hoặc bằng (>=)
  • bgt:          Lớn hơn (>)
  • ble:          Nhỏ hơn hoặc bằng (<=)
  • blt:           Nhỏ hơn

Chúng ta sẽ sửa lại ví dụ ở trên bằng cách thay thế các lệnh so sánh và rẽ nhánh bằng cách lệnh tôi vừa giới thiệu:

Example 1.2:

.assembly extern mscorlib{}
.assembly Branching{}

.method static void main()
{
	.entrypoint
	.maxstack 2
	.locals init (int32,int32,int32)

	ldstr "Enter first number: "
	call void [mscorlib]System.Console::Write(string)
	call string [mscorlib]System.Console::ReadLine()
	call int32 [mscorlib]System.Int32::Parse(string)
	stloc.0

	ldstr "Enter second number: "
	call void [mscorlib]System.Console::Write(string)
	call string [mscorlib]System.Console::ReadLine()
	call int32 [mscorlib]System.Int32::Parse(string)
	stloc.1

	ldloc.0
	ldloc.1
	bgt greater

	ldloc.0
	ldloc.1
	beq equal

	ldstr "First number is less than the second number."
	br print

greater:
	ldstr "First number is greater than the second number."
	br print

equal:
	ldstr "Two number are equal."

print:
	call void [mscorlib]System.Console::WriteLine(string)
	ret
}

Ví dụ này hoạt động tương tự như ví dụ 1.1, ngoài ra còn thêm chức năng kiểm tra 2 giá trị bằng nhau bằng lệnh beq như bạn thấy ở trên.

(Chú ý rằng các lệnh nhảy có điều kiện vì phải sử dụng giá trị trong stack nên giá trị đó sẽ được đưa ra khỏi stack)

Vòng lặp

Vòng lặp được tạo ra bằng cách sử dụng các lệnh rẽ nhánh và điều kiện như trong phần trên. Thay vì nhảy xuống dưới thì ta sẽ thực hiện nhảy lên trên để thực hiện lại một đoạn mã cho đến khi điều kiện phù hợp.

Ta thử viết một ví dụ tính giai thừa của một số nhập từ người dùng.

Example 2.1

.assembly extern mscorlib{}
.assembly Factorial{}

.method static void main() cil managed
{
	.entrypoint
	.maxstack 2
	.locals init (int32 number, int32 counter, int32 result)

	ldc.i4 1
	stloc counter

	ldc.i4 1
	stloc result

	ldstr "Enter a number: "
	call void [mscorlib]System.Console::Write(string)
	call string [mscorlib]System.Console::ReadLine()
	call int32 [mscorlib]System.Int32::Parse(string)

	stloc number

loop:
	ldloc counter
	ldloc number
	bgt print

	ldloc counter
	ldloc result
	mul
	stloc result

	ldc.i4 1
	ldloc counter
	add
	stloc counter

	br loop

print:
	ldloc number
	call void [mscorlib]System.Console::Write(int32)
	ldstr "! = "
	call void [mscorlib]System.Console::Write(string)
	ldloc result
	call void [mscorlib]System.Console::WriteLine(int32)
	ret
}

Các biến cục bộ được khai báo có công dụng sau:

  • number: giá trị cần tính giai thừa (nhập từ người dùng)
  • counter: biến đếm
  • result: kết quả của phép giai thừa

Phần đầu chương trình ta khởi tạo giá trị cho các biến này và bắt đầu thực hiện vòng lặp. Vòng lặp được bắt đầu với nhãn loop.

Trong vòng lặp, đầu tiên ta kiểm tra nếu counter lớn hơn number tức là quá trình tính đã hoàn tất, ta nhảy đến nhãn print để xuất kết quả:

ldloc counter
ldloc number
bgt print

Tiếp theo nhân counter với result và lưu kết quả vào biến result:

ldloc counter
ldloc result
mul
stloc result

Tăng biến counter lên 1 và nhảy về đầu vòng lặp:

ldc.i4 1
ldloc counter
add
stloc counter

br loop

Ví dụ 2.1 này hoàn toàn không sử dụng kiến thức gì khác ngoài những gì tôi đã giới thiệu. Trong các ngôn ngữ bậc cao bạn cũng có thể dùng cách này để tạo ra vòng lặp, tuy nhiên sẽ làm chương trình khó quản lý và không tiện lợi bằng các cấu trúc lặp được hỗ trợ sẵn.

Ngoài cách lặp trên, bạn có thể sử dụng phương thức GetEnumerator() của collection trong thư viện mscorlib để duyệt qua các phần tử của mảng hoặc collection bất kì.

Bài viết liên quan:

MSIL – Part 1: Giới thiệu về MSIL
MSIL – Part 2: Biến cục bộ
MSIL – Part 3: Rẽ nhánh và vòng lặp
MSIL – Part 4: Lập trình hướng đối tượng
MSIL – Part 5: Conversion, casting và boxing
MSIL – Part 6: Sử dụng mảng

https://yinyangit.wordpress.com

Advertisements

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Log Out / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Log Out / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Log Out / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Log Out / Thay đổi )

Connecting to %s