You are on page 1of 55

LẬP TRÌNH HỆ THỐNG

ThS. Đỗ Thị Thu Hiền


(hiendtt@uit.edu.vn)

1
Machine-level programming:
Procedure (Hàm/Thủ tục)

2
Cơ chế gọi hàm/thủ tục (procedure)
 1. Chuyển luồng P(…) {
 Bắt đầu thực thi hàm được gọi •

 Trở về vị trí đã gọi hàm y = Q(x);
 2. Truyền dữ liệu print(y)
 Truyền tham số (arguments) cho hàm •
}
 Nhận giá trị trả về của hàm
 3. Quản lý bộ nhớ
 Cấp phát bộ nhớ khi thực thi hàm int Q(int i)
 Thu hồi bộ nhớ khi thực thi xong {
int t = 3*i;
;
 Tất cả đều thực hiện được ở mức máy int v[10];
tính! •
 Hàm ở IA32 và x86-64 sẽ có một số khác •
biệt. return v[t];
}

3
Cơ chế gọi hàm/thủ tục (procedure)
int main() int func(int x, int y)
{ {
int result = func(5,6); int sum = 0;
return result; sum = x + y;
} return sum;
}
main: func:
pushl %ebp pushl %ebp
movl %esp, %ebp movl %esp, %ebp
subl $16, %esp subl $16, %esp
pushl $6 movl $0, -4(%ebp)
pushl $5 movl 8(%ebp), %edx
call func movl 12(%ebp), %eax
movl %eax, -4(%ebp) addl %edx, %eax
movl -4(%ebp), %eax movl %eax, -4(%ebp)
leave movl -4(%ebp), %eax
ret leave
ret 4
Nội dung
 Thủ tục (Procedures)
 Cấu trúc stack
 Gọi hàm trong IA32
 Chuyển luồng
 Truyền dữ liệu
 Quản lý dữ liệu cục bộ
 Gọi hàm trong x86-64
 Minh hoạ hàm đệ quy

5
IA32 Stack
Vùng nhớ được quản lý theo

Stack “Bottom”
quy tắc ngăn xếp
 First In Last Out
 Phát triển dần về phía địa chỉ Địa chỉ
thấp hơn tăng
đần

 Thanh ghi %esp chứa địa chỉ


thấp nhất của stack
 địa chỉ của “đỉnh” stack
Stack
phát
Con trỏ stack
triển đi
(Stack Pointer): xuống
%esp

Stack “Top”
6
IA32 Stack: Push
 Đẩy dữ liệu vào stack Stack “Bottom”
 pushl Src
 Lấy giá trị từ Src
Địa chỉ
 Giảm %esp xuống 4 bytes tăng
 Ghi giá trị lấy được vào địa chỉ đang lưu đần
trong %esp

Stack
phát
triển đi
Stack Pointer: %esp -4 xuống

Stack “Top”
7
IA32 Stack: Pop
 Lấy dữ liệu từ stack Stack “Bottom”
 popl Dest
 Lấy giá trị ở địa chỉ lưu trong %esp
Địa chỉ
 Đưa giá trị lấy được đưa vào Dest tăng
 Tăng %esp lên 4 bytes đần

Stack
phát
+4 triển đi
Stack Pointer: %esp xuống

Stack “Top”
8
IA32 Stack: Push and Pop – Ví dụ
 %esp = 0x108 Stack
 %eax = 0x1234
 %ebx = 0xABCD Địa chỉ
0x110
tăng
Các thanh ghi và stack thay đổi 0x10c đần
như thế nào khi thực hiện lần
lượt các lệnh sau? 0x108 123
%esp
push 0x104 0x1234
%esp
1. push %eax
%eax, %ebx không đổi Stack
%esp giảm xuống 4 (bytes)
Stack có thêm 1 giá trị là 0x1234 (giá trị của %eax)
Địa chỉ
0x110
tăng
2. pop %ebx 0x10c đần
%eax không đổi 0x108 123
%esp
%esp tăng lên 4 (bytes) pop 0x104 0x1234
Stack mất 1 giá trị là 0x1234 %esp
%ebx được gán bằng giá trị đã lấy từ stack là 0x1234 9
x86-64 Stack?
 Thanh ghi %rsp Stack “Bottom”
 Các lệnh push hay pop
 Thay đổi (cộng/trừ) giá trị %rsp 8
Địa chỉ
bytes tăng
đần

Stack
phát
triển đi
Stack Pointer: %rsp xuống

Stack “Top”
10
Nội dung
 Thủ tục (Procedures)
 Cấu trúc stack
 Gọi hàm trong IA32
 Chuyển luồng
 Truyền dữ liệu
 Quản lý dữ liệu cục bộ
 Gọi hàm trong x86-64
 Minh hoạ hàm đệ quy

11
Chuyển luồng thực thi hàm

804854e: e8 3d 06 00 00 call 8048b90 <main>


8048553: 50 pushl %eax

8048b90 <main>:
….
ret

12
Chuyển luồng thực thi hàm
 Mỗi hàm đều có địa chỉ bắt đầu, thường được gán label
 Stack hỗ trợ gọi hàm và trở về từ hàm
 Gọi 1 hàm con – Procedure call
 Trở về hàm mẹ từ hàm con – Procedure ret
 Gọi hàm: call label
 Lưu địa chỉ trả về (return address) vào stack (push)
 Nhảy đến label để thực thi
 Trở về từ hàm: ret
 Lấy địa chỉ trả về ra từ stack (pop)
 Nhảy đến địa chỉ lấy được để quay về hàm mẹ
 Địa chỉ trả về (Return address):
 Địa chỉ câu lệnh tiếp theo của hàm mẹ cần thực thi ngay phía sau
lệnh call hàm con
804854e: e8 3d 06 00 00 call 8048b90 <main>
 Ví dụ trong mã assembly bên: 8048553: 50 pushl %eax
 Địa chỉ trả về = 0x8048553 13
Ví dụ: Gọi hàm
804854e: e8 3d 06 00 00 call 8048b90 <main>
8048553: 50 pushl %eax
call 0x8048b90 = push %eip
jmp 0x8048b90
trước call call 8048b90

0x110 0x110
0x10c 0x10c
0x108 123 0x108 123
%esp
0x104 0x8048553
%esp

%esp 0x108 %esp 0x104

%eip 0x804854e %eip 0x8048b90


0x8048553
%eip: program counter 14
Ví dụ: Trả về hàm
8048591: c3 ret

ret = pop %eip trước ret ret


jmp *%eip

0x110 0x110
0x10c 0x10c
0x108 123 0x108 123
0x104 0x8048553 0x8048553

%esp 0x104 %esp 0x108


0x104

%eip 0x8048591 %eip 0x8048592


0x8048553

%eip: program counter


15
Gọi và trả về hàm – Ví dụ
int main() int func(int x, int y)
{ {
int result = func(5,6); int sum = 0;
return result; sum = x + y;
} return sum;
}
main: func:
pushl %ebp pushl %ebp
movl %esp, %ebp movl %esp, %ebp
subl $16, %esp subl $16, %esp
pushl $6 movl $0, -4(%ebp)
pushl $5 movl 8(%ebp), %edx
call func movl 12(%ebp), %eax
movl %eax, -4(%ebp) addl %edx, %eax
return movl -4(%ebp), %eax movl %eax, -4(%ebp)
addr leave movl -4(%ebp), %eax
ret leave
ret 16
Hoạt động của hàm dựa trên stack
 Stack được cấp phát bằng Frames
 1 hàm (procedure) = 1 stack frame
 Hỗ trợ lưu trữ các thông tin dùng để gọi và trả về hàm (procedure)
 Địa chỉ trả về
 Các tham số (arguments)
 Các biến cục bộ Local variables
 Quy tắc ngăn xếp
 Trạng thái của 1 procedure trong một khoảng thời gian
 Từ lúc được gọi đến lúc trả về
 Hàm con hoàn thành trước khi hàm mẹ trả về

17
Ví dụ chuỗi gọi hàm
yoo(…) Example
{ Call Chain

• who(…) yoo
who(); {
• • • •
• who
amI(); amI(…)
} • • • {
amI(); • amI amI
• • • •
} amI(); amI


} amI

Procedure amI() is recursive

18
Stack
Ví dụ
yoo(…) yoo
%ebp
{ yoo
yoo
• who Địa
%esp
• chỉ
who(); giảm
• amI amI dần

} amI

amI

19
Stack
Ví dụ
yoo(…) yoo
{ who(…)
•{ yoo
yoo
who Địa
• • • • %ebp chỉ
amI();
who(); giảm
• • • • amI amI who dần
%esp
• amI();
} • • •
} amI

amI

20
Stack
Ví dụ
yoo(…) yoo
{ who(…)
•{ yoo
yoo
amI(…) who Địa
• • • • chỉ
{
amI();
who(); giảm
• who
• • • • amI amI dần

• amI();
• •amI();
• %ebp
}
} • amI amI
• %esp
}
amI

21
Stack
Ví dụ
yoo(…) yoo
{ who(…)
•{ yoo
yoo
amI(…) who Địa
• • • • chỉ
{
amI();
who(); amI(…) giảm
• who
• • • {• amI amI dần

• amI();•
} • •amI();
••
} • amI amI
• amI();
} • %ebp
• amI
}
amI
%esp

22
Stack
Ví dụ
yoo(…) yoo
{ who(…)
•{ yoo
yoo
amI(…) who Địa
• • • • chỉ
{
amI();
who(); amI(…) giảm
• who
• • • {• amI amI dần

• amI();• amI(…)
} • •amI();
• •{
} • • amI amI
• amI();
• •
}
• amI(); amI
} • amI

} %ebp
amI
%esp

23
Stack
Ví dụ
yoo(…) yoo
{ who(…)
•{ yoo
yoo
amI(…) who Địa
• • • • chỉ
{
amI();
who(); amI(…) giảm
• who
• • • {• amI amI dần

• amI();•
} • •amI();
••
} • amI amI
• amI();
} • %ebp
• amI
}
amI
%esp

24
Stack
Ví dụ
yoo(…) yoo
{ who(…)
•{ yoo
yoo
amI(…) who Địa
• • • • chỉ
{
amI();
who(); giảm
• who
• • • • amI amI dần

• amI();
• •amI();
• %ebp
}
} • amI amI
• %esp
}
amI

25
Stack
Ví dụ
yoo(…) yoo
{ who(…)
•{ yoo
yoo
who Địa
• • • • %ebp chỉ
amI();
who(); giảm
• • • • amI amI who dần
%esp
• amI();
} • • •
} amI

amI

26
Stack
Ví dụ
yoo(…) yoo
{ who(…)
•{ yoo
yoo
amI(…) who Địa
• • • • chỉ
{
amI();
who(); giảm
• who
• • • • amI amI dần

• amI();
• •amI();
• %ebp
}
} • amI amI
• %esp
}
amI

27
Stack
Ví dụ
yoo(…) yoo
{ who(…)
•{ yoo
yoo
who Địa
• • • • %ebp chỉ
amI();
who(); giảm
• • • • amI amI who dần
%esp
• amI();
} • • •
} amI

amI

28
Stack
Ví dụ
yoo
yoo(…) %ebp
{ yoo
yoo
who %esp Địa

chỉ
• giảm
who(); amI amI dần


} amI

amI

29
Stack Frames trong IA32
 1 Frame là vùng nhớ xác định bởi
%ebp và %esp Previous
 %ebp trỏ đến vị trí cố định Frame
 %esp lưu động
 Thường truy xuất các dữ liệu trên stack Frame
dựa trên %ebp Pointer:
%ebp
Ví dụ: -4(%ebp) Frame for
proc
Stack
Pointer:
%esp

Stack “Top”

30
IA32/Linux Stack Frame (1)
 Stack frame của hàm mẹ
 Các tham số cho hàm con
 Đưa vào bằng các lệnh push/mov Caller
Frame
 Địa chỉ trả về (Return address)
Arguments
 Tự động đẩy vào stack khi chạy instruction call

 Stack Frame của 1 hàm (“Bottom” to “Top”) Return Addr


Old %ebp
 Frame pointer của hàm mẹ (%ebp) Frame pointer
 Những thanh ghi được lưu lại %ebp Saved
 Các biến cục bộ của hàm Registers
+
 “Argument build” Local
Tham số cho các hàm muốn gọi (nếu có) Variables

Argument
Stack pointer
Build
%esp
31
IA32/Linux Stack Frame (2)
 Quản lý frame
 Cấp phát không gian khi bắt đầu thực thi hàm
Caller
 “Set-up” code trong assembly
Frame
 Thu hồi không gian khi hàm trả về Arguments
 “Finish” code trong assembly
Return Addr
Old %ebp
Frame pointer
%ebp
Saved
Registers
+
Local
Variables

Argument
Stack pointer
Build
%esp
32
IA32 Stack frame - Set up & Finish
 Stack Frame – Set up
 Khi 1 hàm bắt đầu thực thi swap:
 Lưu lại %ebp của hàm trước pushl %ebp
movl %esp, %ebp Set
 Thiết lập %ebp cho stack frame của nó pushl %ebx Up
 Lưu lại các thanh ghi sẽ sử dụng trong hàm
(nếu có) movl 8(%ebp), %edx
movl 12(%ebp), %ecx
movl (%edx), %ebx
movl (%ecx), %eax
 Stack frame - Finish movl %eax, (%edx)
movl %ebx, (%ecx)
 Khi 1 hàm chuẩn bị trả về
 Khôi phục giá trị cũ của các thanh ghi đã sử popl %ebx
dụng (nếu có) popl %ebp Finish
 Khôi phục %ebp của hàm trước ret

33
Stack frame set up – Ví dụ
Stack sau khi thực hiện call example: %ebx = 0x10
%esp = 0x104, %ebp = 0x11C
%ebp 0x11C example:
pushl %ebp
movl %esp, %ebp Set
pushl %ebx Up
movl
subl $20, %esp
0x110
Arguments movl 8(%ebp), %edx
0x108 movl 12(%ebp), %ecx
%esp Return Addr 0x104
push 1 movl (%edx), %ebx
%ebp %esp 0x11C movl (%ecx), %eax
0x100
push 2 0x10 movl %eax, (%edx)
%esp 0xFC
movl %ebx, (%ecx)

subl
addl $20, %esp
popl %ebx Finish
%esp 0xE8 popl %ebp
Stack sau set-up stack frame của example: ret
%esp = 0xE8, %ebp = 0x100 34
Stack frame Finish – Ví dụ
%ebx == ??
0x10

%ebp example:
0x11C
pushl %ebp
movl %esp, %ebp Set
pushl %ebx Up
popl 2 subl $20, %esp
0x110
Arguments movl 8(%ebp), %edx
0x108 movl 12(%ebp), %ecx
%esp Return Addr 0x104
movl (%edx), %ebx
popl 2 0x11C movl (%ecx), %eax
%ebp %esp 0x100
movl %eax, (%edx)
popl 1 0x10
%esp 0xFC movl %ebx, (%ecx)

addl addl $20, %esp
popl %ebx Finish
%esp 0xE8 popl %ebp
Stack sau finish stack frame của example: ret
%esp = 0x104, %ebp = 0x11C 35
Stack frame set up & Finish – Ví dụ
int func(int x, int y)
{
int sum = 0;
func:
sum = x + y;
pushl %ebp Set
return sum; movl %esp, %ebp
} Up
subl $16, %esp

movl $0, -4(%ebp)


movl 8(%ebp), %edx
movl 12(%ebp), %eax
addl %edx, %eax
movl %eax, -4(%ebp)
movl -4(%ebp), %eax
- Gán %esp = %ebp
leave
- Pop %ebp từ stack Finish
ret

36
Nội dung
 Thủ tục (Procedures)
 Cấu trúc stack
 Gọi hàm trong IA32
 Chuyển luồng
 Truyền dữ liệu
 Quản lý dữ liệu cục bộ
 Gọi hàm trong x86-64
 Minh hoạ hàm đệ quy

37
Truyền tham số trong Stack frame IA32
 Hàm mẹ (caller) đưa tham số vào stack
cho hàm con (callee)

Argument n
 Trước khi thực thi call label
Caller
 Lệnh push/mov Frame Argument 3
 Nằm ngay phía trước địa chỉ trả về (return Argument 2
address) trong stack Argument 1
Return Addr
 Thứ tự: reverse order Frame pointer
Old %ebp
%ebp
 Hàm con (callee) truy xuất tham số Saved
Registers
 Dựa trên vị trí so với %ebp của hàm con +
 %ebp sau khi hoàn thành “set up” code Local
 Thông thường: Variables
 Tham số 1: vị trí %ebp+8
Argument
 Tham số 2: vị trí %ebp+12 Stack pointer
Build
 Tham số thứ n: vị trí %ebp+4*(n+1) %esp
38
Truyền tham số: Ví dụ swap
Gọi swap từ hàm call_swap
int course1 = 15213; call_swap:
int course2 = 18243; • • •
subl $8, %esp
void call_swap() { movl $course2, 4(%esp)
swap(&course1, &course2); movl $course1, (%esp)
} call swap
• • •

Stack
void swap(int *xp, int *yp)
{ •
int t0 = *xp; •
int t1 = *yp; •
*xp = t1; %esp
*yp = t0; &course2 subl
} &course1
%esp
Rtn adr call
%esp 39
Truyền tham số: Ví dụ swap
swap:
pushl %ebp
void swap(int *xp, int *yp) movl %esp, %ebp Set
{ pushl %ebx Up
int t0 = *xp;
int t1 = *yp; movl 8(%ebp), %edx
*xp = t1; movl 12(%ebp), %ecx
*yp = t0; movl (%edx), %ebx Body
} movl (%ecx), %eax
movl %eax, (%edx)
movl %ebx, (%ecx)

popl %ebx
popl %ebp Finish
ret

40
swap Setup #1
Entering Stack Resulting Stack
%ebp %ebp
• •
• •
• •

&course2 yp
&course1 xp
Rtn adr Rtn adr
%esp
Old %ebp
%esp

swap:
pushl %ebp
movl %esp,%ebp
pushl %ebx
41
swap Setup #2
Entering Stack Resulting Stack
%ebp
• •
• •
• •

&course2 yp
&course1 xp
Rtn adr Rtn adr
%esp
Old %ebp %ebp
%esp

swap:
pushl %ebp
movl %esp,%ebp
pushl %ebx
42
swap Setup #3
Entering Stack Resulting Stack
%ebp
• •
• •
• •

&course2 yp
&course1 xp
Rtn adr Rtn adr
%esp
Old %ebp
%ebp
Old %ebx
%esp
swap:
pushl %ebp
movl %esp,%ebp
pushl %ebx
43
swap: Lấy các tham số
Entering Stack Resulting Stack
%ebp
• •
• •
• •
Offset relative to %ebp
&course2 12 yp
&course1 8 xp
Rtn adr %esp 4 Rtn adr
Old %ebp
%ebp
Old %ebx
%esp
movl 8(%ebp),%edx # get xp
movl 12(%ebp),%ecx # get yp
. . .

44
Truyền tham số cho hàm – Ví dụ
int main() int func(int x, int y)
{ {
int result = func(5,6); int sum = 0;
return result; sum = x + y;
} return sum;
}
main: func:
pushl %ebp pushl %ebp
movl %esp, %ebp movl %esp, %ebp
subl $16, %esp subl $16, %esp
pushl $6 movl $0, -4(%ebp)
pushl $5 movl 8(%ebp), %edx
call func movl 12(%ebp), %eax
addl $8, %esp addl %edx, %eax
movl %eax, -4(%ebp) movl %eax, -4(%ebp)
movl -4(%ebp), %eax movl -4(%ebp), %eax
leave leave
ret ret 45
Giá trị trả về từ hàm
 Hàm có trả về giá trị int Q(int i)
 Trong C: qua lệnh return x. {
int t = 3*i;
;
int v[10];


 Giá trị trả về của hàm trong assembly return v[t];
}
 Thường lưu trong thanh ghi %eax

46
Giá trị trả về từ hàm – Ví dụ
int main() int func(int x, int y)
{ {
int result = func(5,6); int sum = 0;
return result; sum = x + y;
} return sum;
}
main: func:
pushl %ebp pushl %ebp
movl %esp, %ebp movl %esp, %ebp
subl $16, %esp subl $16, %esp
pushl $6 movl $0, -4(%ebp)
pushl $5 movl 8(%ebp), %edx
call func movl 12(%ebp), %eax
addl $8, %esp addl %edx, %eax
movl %eax, -4(%ebp) movl %eax, -4(%ebp)
movl -4(%ebp), %eax movl -4(%ebp), %eax
leave leave
ret ret 47
Nội dung
 Thủ tục (Procedures)
 Cấu trúc stack
 Gọi hàm trong IA32
 Chuyển luồng
 Truyền dữ liệu
 Quản lý dữ liệu cục bộ
 Gọi hàm trong x86-64
 Minh hoạ hàm đệ quy

48
Sử dụng thanh ghi cho trong hàm

 Giả sử yoo là hàm mẹ, gọi hàm who


 Có thể dùng thanh ghi để lưu trữ tạm?
yoo: who:
• • • • • •
movl $15213, %edx movl 8(%ebp), %edx
call who addl $18243, %edx
addl %edx, %eax • • •
• • • ret
ret

 Giá trị của thanh ghi %edx bị ghi đè trong hàm who
 Có thể gây ra vấn đề  cần lưu lại!

49
Quy ước lưu các thanh ghi
 Giả sử yoo gọi who:
 yoo là hàm mẹ (caller)
 who là hàm con (callee)
 Quy ước
 “Caller Save”
 Hàm mẹ lưu lại các giá trị tạm thời trong stack frame của nó
trước khi gọi hàm con
 “Callee Save”
 Hàm con lưu lại các giá trị tạm thời trong stack của nó trước khi
sử dụng

50
Sử dụng các thanh ghi IA32/Linux + Windows
 %eax, %edx, %ecx
 Hàm mẹ lưu trước khi gọi nếu
giá trị sẽ được sử dụng tiếp %eax
Caller-Save
 %eax Temporaries %edx
 được sử dụng để trả về giá trị số %ecx
nguyên
%ebx
 %ebx, %esi, %edi Callee-Save
 Hàm con sẽ lưu nếu muốn sử Temporaries %esi
dụng %edi
 %esp, %ebp %esp
Special
 Trường hợp đặc biệt cần hàm %ebp
con lưu
 Khôi phục lại giá trị ban đầu
trước khi thoát hàm
51
Khởi tạo biến cục bộ: Ví dụ
 Biến cục bộ
 Cấp phát vùng nhớ trong stack để lưu các biến cục bộ của hàm
 Truy xuất dựa trên %ebp
 Địa chỉ thấp hơn so với %ebp
int add3(int x) { 8 x
int localx = x;
incrk(&localx, 3); 4 Rtn adr
return localx; 0 Old %ebp
} %ebp
-4 localx = x
First part of add3
-8
add3:
-12 Unused
pushl%ebp
movl %esp, %ebp -16
subl $24, %esp # Alloc. 24 bytes -20
movl 8(%ebp), %eax
movl %eax, -4(%ebp) # Set localx to x -24
%esp
52
Biến cục bộ – Ví dụ
int main() int func(int x, int y)
{ {
int result = func(5,6); int sum = 0;
return result; sum = x + y;
} return sum;
}
6 func:
pushl %ebp
5
movl %esp, %ebp
Rtn adr subl $16, %esp
Old %ebp movl $0, -4(%ebp)
%ebp movl 8(%ebp), %edx
sum = 0
%ebp-4 movl 12(%ebp), %eax
addl %edx, %eax
movl %eax, -4(%ebp)
movl -4(%ebp), %eax
%esp leave
ret 53
Gọi hàm (IA32): Tổng kết
 Stack đóng vai trò quan trọng trong gọi/trả về hàm
 Lưu trữ địa chỉ trả về
 Các tham số (trong stack frame hàm mẹ)
 Có thể lưu các giá trị trong stack frame hoặc các thanh ghi
 Giá trị trả về ở thanh ghi %eax

54
Bài tập gọi hàm 1
main: function:
pushl %ebp pushl %ebp
movl %esp, %ebp movl %esp, %ebp
subl $16, %esp subl $16, %esp
movl $1, -4(%ebp) movl $10, -4(%ebp) # a
movl $2, -8(%ebp) movl -4(%ebp), %edx
movl $0, -12(%ebp) movl 8(%ebp), %eax # x
pushl -4(%ebp) addl %eax, %edx
pushl -8(%ebp) movl 12(%ebp), %eax
call function imull %edx, %eax
addl $8, %esp movl %eax, -8(%ebp)
movl %eax, -12(%ebp) movl -8(%ebp), %eax
movl $0, %eax leave
leave ret
ret
3. Hàm function nhận bao nhiêu tham số?
1. Hàm nào là caller/callee? 2 tham số ở %ebp + 8 và %ebp + 12
main là caller (hàm mẹ), function là callee 4. Hàm main đã truyền tham số như thế nào cho
(hàm con) function?
2. Mỗi hàm có bao nhiêu biến cục bộ? Giá Tham số thứ nhất là 2, tham số thứ 2 là 1
trị như thế nào? 5. Hàm function làm gì và trả về giá trị bao nhiêu
- main có 3 biến cục bộ có giá trị là 1, 2, 0. cho main?
- function có 2 biến cục bộ có giá trị là 10 Giả sử function(x,y): Tính (x + 10)*y
và 1 biến được gán kết quả tính toán. Trả về giá trị 12
55

You might also like