Tải bản đầy đủ (.pdf) (69 trang)

Phân tích và chuyển đổi mã nguồn c c++ từ hệ điều hành windows 32 BIT sang hệ điều hành windows 64BIT

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (895.67 KB, 69 trang )

..

BỘ GIÁO DỤC VÀ ĐÀO TẠO
TRƯỜNG ĐẠI HỌC BÁCH KHOA HÀ NỘI
---------------------------------------

ĐỒN QUANG TRUNG

Phân tích và chuyển đổi mã nguồn C/C++ từ hệ điều hành Windows
32 bit sang hệ điều hành Windows 64 bit

CHUYÊN NGÀNH: CÔNG NGHỆ THÔNG TIN
MÃ SỐ HỌC VIÊN: CB150299
LUẬN VĂN THẠC SĨ CÔNG NGHỆ THÔNG TIN

NGƯỜI HƯỚNG DẪN KHOA HỌC
TS. BAN HÀ BẰNG

HN - 2018


LỜI CAM ĐOAN
Tôi xin cam đoan luận văn là công trình nghiên cứu của riêng cá nhân
tơi, khơng sao chép của ai do tôi tự nghiên cứu, đọc, dịch tài liệu, tổng hợp và
thực hiện. Nội dung lý thuyết trong trong luận văn tơi có sử dụng một số tài liệu
tham khảo như đã trình bày trong phần tài liệu tham khảo. Các số liệu, chương
trình phần mềm và những kết quả trong luận văn là trung thực và chưa được
cơng bố trong bất kỳ một cơng trình nào khác.

HN ngày 20, tháng 09 năm 2018
Học viên thực hiện


Đoàn Quang Trung


LỜI CẢM ƠN
Trước khi trình bày nội dung chính của luận văn, em xin bày tỏ lòng biết
ơn sâu sắc tới TS. Ban Hà Bằng người đã tận tình chỉ bảo, hướng dẫn và giúp đỡ
em trong suốt quá trình hoàn thành luận văn này.
Em xin chân thành cảm ơn tập thể cán bộ Khoa Công Nghệ Thông Tin,
Viện Đào Tạo Sau Đại học, Trường Đại học Bách khoa Hà Nội, đã tạo mọi điều
kiện thuận lợi cho em trong quá trình học tập và nghiên cứu.
Nhân dịp này em cũng xin được gửi lời cảm ơn chân thành tới gia đình,
bạn bè đã ln bên em cổ vũ, động viên, giúp đỡ em trong suốt quá trình học tập
và thực hiện luận văn.
Xin chân thành cảm ơn!
Hà Nội, ngày 20 tháng 09 năm 2018
Học viên

Đoàn Quang Trung


MỤC LỤC
LỜI CAM ĐOAN
LỜI CẢM ƠN
CÁC HÌNH VẼ, BẢNG BIỂU TRONG LUẬN VĂN
THUẬT NGỮ TIẾNG ANH
MỞ ĐẦU .........................................................................................................................1
Chương 1: Tổng quan về hệ điều hành 32bit và 64bit ....................................................2
1.1.Các khái niệm cơ bản về x86, x86-64, IA-64, 32bit và 64 bit ...................2
1.2.Hoạt động của CPU....................................................................................2
1.3.Vai trò của 32-bit hay 64bit trong kiến trúc của CPU ...............................3

1.4.Lợi ích và hạn chế của vi xử lý 64-bit so với 32-bit ..................................4
1.4.1.Lợi ích ..................................................................................................4
1.4.2.Nhược điểm ..........................................................................................5
Chương 2: Ngơn ngữ lập trình C/C++ và những vấn đề phát sinh khi dịch chuyển ứng
dụng nền tảng 32bit lên 64 bit .........................................................................................6
2.1.Ngơn ngữ lập trình C và C++.....................................................................6
2.1.1.Ngơn ngữ lập trình C ...........................................................................6
2.1.2.Ngơn ngữ lập trình C++ .......................................................................7
2.1.3.Sự khác nhau cơ bản giữa C và C++ ...................................................8
2.2.Đa luồng (Multithread) trong C++ .............................................................9
2.2.1.Khái niệm đa luồng ..............................................................................9
2.2.2.Đa luồng trong C++ .............................................................................9
2.3.Các phương pháp hiện có phát hiện bẫy lỗi khi chuyển mã code C và C
++ sang Windows 64 bit ................................................................................10
2.3.1.Review code .......................................................................................11
2.3.2.Phân tích code tĩnh .............................................................................11
2.3.3.Phân tích code động ...........................................................................12
2.3.4.Phương pháp hộp trắng ......................................................................12
2.3.5.Phương pháp hộp đen.........................................................................13
2.3.6.Phương pháp kiểm thử thủ công ........................................................13
2.4.Nguyên nhân và phương pháp chi tiết .....................................................14
2.4.1.Tắt cảnh báo (Disabled warnings) .....................................................14
2.4.2.Sử dụng những hàm với số lượng của những đối số thay đổi ...........14
2.4.3.Số ma thuật.........................................................................................15


2.4.4.Lưu trữ số nguyên trong kiểu double .................................................17
2.4.5.Phép toán dịch bit...............................................................................18
2.4.6.Lưu trữ các giá trị của con trỏ ............................................................19
2.4.7.Memsize kiểu trong union. ................................................................21

2.4.8.Thay đổi một kiểu mảng ....................................................................23
2.4.9.Hàm ảo với các đối số kiểu memsize .................................................24
2.4.10.Quá trình serialization (tuần tự) và trao đổi dữ liệu .........................26
2.4.11.Trường bit ........................................................................................28
2.4.12.Gợi ý phép tính địa chỉ .....................................................................31
2.4.13.Chuỗi chỉ số .....................................................................................34
2.4.14. Sự pha trộn của việc sử dụng các kiểu số nguyên đơn giản và kiểu
memsize........................................................................................................36
2.4.15. Sự chuyển đổi Kiểu ẩn trong khi dùng hàm ....................................39
2.4.16. Vấn đề nạp chồng hàm ....................................................................40
2.4.17. Căn chỉnh dữ liệu ............................................................................42
2.4.18. Các ngoại lệ .....................................................................................45
2.4.19. Sử dụng hàm đã lỗi thời & hằng số định nghĩa trước .....................46
2.4.20. Ép kiểu tường minh .........................................................................46
Chương 3: Thực nghiệm ................................................................................................47
3.1.Xây dựng công cụ ....................................................................................47
3.1.1.Đề xuất giải pháp chuyển đổi mã nguồn c/c++ từ hệ điều hành 32 bit
sang 64 bit ....................................................................................................47
3.1.2.Giao diện chương trình ......................................................................49
3.2.Ví dụ thực nghiệm khắc phục cảnh báo C4996 .......................................51
3.2.1.Nguyên nhân dẫn đến cảnh báo C4996..............................................51
3.2.2.Các thể hiện cụ thể .............................................................................52
3.2.3.Các cách giải quyết vấn đề .................................................................52
3.3.Kết quả thực nghiệm ................................................................................53
KẾT LUẬN ........................................................................................................60
Tài liệu tham khảo ..............................................................................................62


CÁC HÌNH VẼ, BẢNG BIỂU TRONG LUẬN VĂN
Hình 2.1 Lưu trữ số ngun trong kiểu double ................................................... 18

Hình 2.2 Tính tốn giá trị Mask ......................................................................... 19
Hình 2.3 Thay đổi một kiểu mảng ...................................................................... 24
Hình 2.4 Thứ tự sắp xếp byte trong hệ điều hành 64-bit trên kết thúc lớn và kết
thúc nhỏ. .............................................................................................................. 29
Hình 2.5 Tính tốn biểu thức trên hệ điều hành khác. ........................................ 31
Hình 2.6 Sự thay đổi diễn ra trong biểu thức. ..................................................... 40
Hình 3.1 Lưu đồ thuật tốn ................................................................................. 49
Hình 3.2 Màn hình chính..................................................................................... 50
Hình 3.3 Màn hình sau khi hồn thành ............................................................... 52
Hình 3.4 Danh sách các cảnh báo được xử lý ..................................................... 58

Bảng 1.1 Dung lượng RAM tối đa 1 số hệ điều hành cơ bản hỗ trợ .................... 4
Bảng 2.1 So sánh sự khác nhau giữa C và C++ .................................................... 8
Bảng 2.2 B2 chứa số ma thuật cơ bản có thể ảnh hưởng đến tính khả thi của một
ứng dụng trên một nền tảng mới. ........................................................................ 16
Bảng 2.3 B4: Biểu thức dịch bit và kết quả trên hệ thống 64bit ......................... 19
Bảng 3.1 Kết quả thực nghiệm ............................................................................ 59


THUẬT NGỮ TIẾNG ANH
32- bit

Hệ điều hành 32 bit

64 - bit

Hệ điều hành 64 bit

Register


Thanh ghi

ALU

Arithmetic logic unit - bộ xử lý tính
tốn

Function driven

Lập trình hướng chức năng

Object driven

Lập trình hướng đối tượng


MỞ ĐẦU
Ngày nay, hầu hết các ứng dụng đều đã chạy thông trên nền tảng hệ
điều hành Windows 32 bit như Windows NT, Windows 2000, Windows XP
cũng với các phần mềm viết trên mã nguồn dựa trên bộ Visual Studio của
chính hãng Microsoft. Tuy nhiên, khi chuyển đổi sang hệ điều hành Windows
64 bit như Windows 10 thì gặp vấn đề về tương thích mã nguồn. Lý do là mã
nguồn viết cho các ứng dụng trên nền tảng hệ điều hành Windows 32 bit có
đơn vị xử lý là 32 bit trong khi đối với trình dịch bộ Visual Studio trong hệ
điều hành Windows 64 bit thì đơn vị xứ lý lại là 64 bit. Các lỗi cảnh báo
(Warning) là lỗi của bộ phân tích ngữ nghĩa (semantic analyzer) giải thích
cách xử lý khác nhau của trình dịch đối với các đối tượng (biến, hằng, hàm,
tên gọi…) của mã nguồn viết trên hệ điều hành 64 bit so với 32 bit. Mục tiêu
của đề tài là sử dụng các công cụ trình dịch hiện có để tự động phân tích và
sinh mã nguồn C/C++ mới cho hệ điều hành Windows 64 bit từ các mã nguồn

C/C++ hiện có vốn viết cho hệ điều hành Windows 32 bit, sao cho giảm thiểu
số lỗi cảnh báo.
Nhận thấy tầm quan trọng của việc sinh mã tự động để giảm hiểu công
sức và thời gian của lập trình viên, em đã tìm hiểu và cùng với sự hướng dẫn
tận tình, giúp đỡ của TS. Ban Hà Bằng em đã chọn đề tài "Phân tích và
chuyển đổi mã nguồn C/C++ từ hệ điều hành Windows 32 bit sang hệ điều
hành Windows 64 bit".
Bố cục luận văn gồm 3 chương sau:
• Chương 1: Tổng quan về hệ điều hành 32bit và 64bit
• Chương 2: Những vấn đề phát sinh khi dịch chuyển ứng dụng nền
tảng 32-bit lên 64-bit
• Chương 3: Cơ sở lý thuyết và ứng dụng.

1


CHƯƠNG 1

TỔNG QUAN VỀ HỆ ĐIỀU HÀNH 32BIT VÀ 64BIT

Mục đích của chương này là giới thiệu sự khác biệt giữa hệ điều hành
32bit về 64bit cũng như ưu nhược điểm của chúng
1.1. Các khái niệm cơ bản về x86, x86-64, IA-64, 32bit và 64 bit
-

x86: Đây là tên của một bộ tập lệnh chỉ dẫn do Intel và AMD phát triển. Nó

được xây dựng dựa trên vi xử lí Intel 8086, một con chip ra mắt năm 1978. Lý do
có chữ "x86" đó là vì những model kế nhiệm cho 8086 cũng được đặt tên có đi
là "86". Trước đây x86 hỗ trợ điện toán 16-bit, giờ đây là 32-bit

-

x86-64: cịn có tên khác là x64, x86_64 hoặc amd64. Đây là cái tên

dùng để chỉ phiên bản 64-bit của tập chỉ dẫn x86. Cấu hình x86-64 ban đầu
được tạo ra bởi AMD. Lúc mới ra mắt, AMD gọi x86-64 là AMD64, Intel thì
gọi bằng cái tên IA-32e và EMT64. Cách gọi tập chỉ dẫn này cũng khác nhau
với các hãng phần mềm, Apple thì gọi là x86_64, Sun Microsystem (giờ đã
thuộc về Oracle) và Microsoft thì gọi là x64, nhiều bản distro Linux lại dùng
chữ AMD64. AMD K8 là vi xử lí đầu tiên trên thế giới sử dụng x86-64.
-

IA-64: kiến trúc này sử dụng trong dòng vi xử Intel Itanium, chủ yếu

xuất hiện trong những máy chủ doanh nghiệp hoặc hệ thống tính tốn hiệu
năng cao chứ ít dùng trong máy tính cá nhân. IA-64 hồn tồn khác biệt và
cũng khơng tương thích với các lệnh của tập lệnh x86 hay x86-64.
-

32-bit và 64-bit: Hai con số này dùng để chỉ độ dài thanh ghi (register)

của CPU và là một loại hình điện tốn chứ khơng phải là một tập kiến trúc
lệnh như ba thứ trên. Người ta có thể dùng khái niệm 32-bit và 64-bit để chỉ
CPU hoặc phần mềm.
1.2.

Hoạt động của CPU

CPU gồm nhiều bộ phận, nhưng ở nghiên cứu này chúng ta sẽ miêu tả
hoạt động của nó qua 2 thành phần chính: ALU (Arithmetic logic unit - bộ xử


2


lý tính tốn) và registers (thanh ghi). Register, ALU, và một số linh kiện khác
hợp thành một nhân CPU.
-

ALU có thể thực hiện những phép tốn thơng thường như cộng trừ,

nhân, chia; ngồi ra cịn có thể chạy những phép logic như so sánh 'hoặc', 'và'.
-

Register là một loại bộ nhớ được tích hợp thẳng vào CPU và nó có tốc

độ hoạt động bằng hoặc gần sát với CPU. Dung lượng của register thường rất
nhỏ. Những CPU 32-bit sẽ có bộ ALU và register với độ rộng là 32-bit, còn
CPU 64-bit thì sẽ có độ rộng của những thành phần này là 64-bit.
1.3. Vai trò của 32-bit hay 64bit trong kiến trúc của CPU
-

Đó là độ dài của register đồng thời là độ dài của địa chỉ mà CPU sử dụng.

+ Độ dài thanh ghi càng lớn thì đem lại kết quả tính tốn cao hơn. Ví dụ:
Để thực hiện phép tính 12+34 trên CPU có thanh ghi độ dài 1, ALU sẽ phải
thực hiện 2 phép tính: 1+3 và 2+4, sau đó nối kết quả lại với nhau. Nếu CPU
có độ dài là 2 thì nó sẽ có thể thực hiện phép tính 12+34 và cho ra kết quả
ngay lập tức, và khơng phải tính từng bước.
+ Độ dài địa chỉ càng lớn thì khả năng lưu trữ cang cao. Với kiến trúc
32bit ta sẽ có 232 = 4294967296 số địa chỉ (khoảng 4,29 tỉ địa chỉ) và cũng

tương ứng với dung lượng 4GB. Trong trường hợp này, nếu có nhiều hơn
4GB RAM thì phần nhiều hơn đó sẽ khơng có địa chỉ, và CPU khơng cách gì
"liên lạc" đến chúng được.
Do nhu cầu sử dụng bộ nhớ ngày càng lớn nên cần đến kiến trúc 64bit.
Khi đó số địa chỉ có thể sử dụng 264 = 1.8446744 x 10^19 tương đương dung
lượng 16 Exbibyte địa chỉ lớn hơn rất rất rất nhiều so với kiến trúc 32-bit
Bên dưới là bảng dung lượng RAM tối đa mà Windows XP, Windows 7,
Windows 8 và Win 10 có thể hỗ trợ.

3


Bảng 1.1 Dung lượng RAM tối đa 1 số hệ điều hành cơ bản hỗ trợ
Phiên bản

Bản 32-bit

Bản 64-bit

Windows XP

4Gb

128Gb

Windows 7 Ultimate

4Gb

192Gb


Windows 7 Enterprise

4Gb

192Gb

Windows 7 Professional

4Gb

192Gb

Windows 7 Home Basic

4Gb

8Gb

Windows 8 Enterprise

4Gb

512Gb

Windows 8 Professional

4Gb

512Gb


Windows 10 Enterprise

4Gb

2TB

Windows 10 Pro

4Gb

2TB

Windows 10 Home

4Gb

128Gb

1.4. Lợi ích và hạn chế của vi xử lý 64-bit so với 32-bit
1.4.1. Lợi ích
-

Quản lý bộ nhớ tốt hơn: Sử dụng được nhiều Ram, nên có thể tận dụng

được tối đa RAM trên máy tính
Ví dụ: nếu như máy tính có 6GB Ram thì phiên bản 64bit sẽ sử dụng được
hết 6GB ram đó. Cịn với bản 32bit thì bạn chỉ nhận được tối đa là 3.4GB.
-


Nâng cao năng suất làm việc: Việc tận dụng được hết RAM là một thế

mạnh của bản 64bit, chính vì thế mà máy tính chắc chắn sẽ xử lý nhanh hơn
và khơng có tình trạng chậm, lag vì thiếu RAM
-

Khả năng phân phối RAM cho từng ứng dụng tốt hơn: Win x86 chỉ

cung cấp tối đa cho mỗi ứng dụng là 2GB Ram chính vì thế mà khi sử dụng
các ứng dụng nặng như thiết kế đồ họa, làm video, sử dụng thiết kết mơ
hình 3D… thì sẽ khơng đáp ứng được và gây treo máy. Nhưng với phiên bản
x64 thì lại khác, nó hỗ trợ lên đến 8TB cho mỗi ứng dụng.

4


-

Tính bảo mật cao hơn: Dựa vào những tính năng như Kernel Patch

Protection hỗ trợ bảo vệ phần cứng và thực hiện sao lưu dữ liệu, loại bỏ các
trình điều khiển của hệ thống 16-bit có sẵn.
1.4.2.
-

Nhược điểm

Việc địa chỉ của dữ liệu dài gấp đơi sẽ gây khơng ít bất lợi cho việc

thiết kế hệ thống điện toán

-

Nhiều thiết bị phần cứng và phần mềm cũ khơng tương thích

5


CHƯƠNG 2

NGƠN NGỮ LẬP TRÌNH C/C++ VÀ NHỮNG VẤN ĐỀ

PHÁT SINH KHI DỊCH CHUYỂN ỨNG DỤNG NỀN TẢNG 32BIT
LÊN 64 BIT
Chương này mơ tả q trình chuyển một ứng dụng 32-bit sang các hệ
thống 64 bit, quá trình này sẽ xuất hiện rất nhiều lỗi mới. Đây là những khó
khăn không thể tránh được mà các nhà phát triển của bất kì chương trình phát
triển nào sẽ phải đối mặt. Trong chương này sẽ hệ thống lại những khó khăn
đó và đưa ra các giải pháp để vượt qua :
-

Lỗi lập trình điển hình xảy ra trên các hệ thống 64-bit;

-

Nguyên nhân của các lỗi này, với các ví dụ tương ứng;

-

Phương pháp sửa lỗi;


-

Xem xét các phương pháp và phương tiện tìm kiếm lỗi trong các

chương trình 64-bit.
-

Tránh các lỗi trong khi viết mã cho các hệ thống 64-bit;

-

Đẩy nhanh quá trình di chuyển một ứng dụng 32-bit sang kiến trúc 64-

bit thông qua việc giảm thời gian cần thiết để gỡ lỗi và thử nghiệm;
2.1.

Ngơn ngữ lập trình C và C++

2.1.1. Ngơn ngữ lập trình C
Ngơn ngữ lập trình C là một ngơn ngữ mệnh lệnh được phát triển từ đầu
thập niên 1970 bởi Dennis Ritchie để dùng trong hệ điều hành UNIX. Từ đó,
ngơn ngữ này đã lan rộng ra nhiều hệ điều hành khác và trở thành một những
ngôn ngữ phổ dụng nhất. C là ngôn ngữ rất có hiệu quả và được ưa chuộng
nhất để viết các phần mềm hệ thống, mặc dù nó cũng được dùng cho việc viết
các ứng dụng. Ngoài ra, C cũng thường được dùng làm phương tiện giảng dạy
trong khoa học máy tính mặc dù ngơn ngữ này khơng được thiết kế dành cho
người nhập môn.
C là một ngôn ngữ lập trình tương đối nhỏ gọn vận hành gần với phần
cứng và nó giống với ngơn ngữ Assembler hơn hầu hết các ngôn ngữ bậc cao.
Hơn thế, C đôi khi được đánh giá như là "có khả năng di động", cho thấy sự

6


khác nhau quan trọng giữa nó với ngơn ngữ bậc thấp như là Assembler, đó là
việc mã C có thể được dịch và thi hành trong hầu hết các máy tính, hơn hẳn
các ngơn ngữ hiện tại trong khi đó thì Assembler chỉ có thể chạy trong một số
máy tính đặc biệt. Vì lý do này C được xem là ngơn ngữ bậc trung.
2.1.2. Ngơn ngữ lập trình C++
C++ (đọc là "C cộng cộng" hay "xi-plus-plus", IPA: /siː pləs pləs/) là một
loại ngơn ngữ lập trình. Đây là một dạng ngơn ngữ đa mẫu hình tự do có kiểu
tĩnh và hỗ trợ lập trình thủ tục, dữ liệu trừu trượng, lập trình hướng đối tượng,
và lập trình đa hình, đồng thời cung cấp các công cụ để can thiệp sâu vào bộ
nhớ. Từ thập niên 1990, C++ đã trở thành một trong những ngơn ngữ thương
mại ưa thích và phổ biến nhất của lập trình viên.
C++ được thiết kế với ưu tiên cho lập trình hệ thống và các hệ thống
nhúng, tài nguyên hạn chế và lớn, với hiệu suất, hiệu quả và tính linh hoạt
cao. C ++ có thể tìm thấy ở mọi nơi, với những điểm mạnh là cơ sở hạ tầng
phần mềm phong phú và các ứng dụng hạn chế tài nguyên, bao gồm các ứng
dụng dành cho máy tính để bàn, máy chủ (ví dụ: thương mại điện tử, tìm kiếm
trên web hoặc máy chủ SQL) và các ứng dụng quan trọng về hiệu suất (ví dụ:
cơng tắc điện thoại hoặc thiết bị thăm dị khơng gian). C ++ là một ngơn ngữ
được biên dịch, có thể sử dụng trên nhiều nền tảng.
Bjarne Stroustrup của Bell Labs đã phát triển C++ (mà tên nguyên thủy là
"C với các lớp" trong suốt thập niên 1980 như là một bản nâng cao của ngôn
ngữ C. Những bổ sung nâng cao bắt đầu với sự thêm vào của khái niệm lớp,
tiếp theo đó là các khái niệm hàm ảo, chồng toán tử, đa kế thừa, tiêu bản, và
xử lý ngoại lệ. Tiêu chuẩn của ngôn ngữ C++ đã được thông qua trong năm
1998 như là ISO/IEC 14882:1998. Phiên bản hiện đang lưu hành là phiên bản
C++14, ISO/IEC 14882:2014.
Nhiều ngôn ngữ lập trình khác được phát triển dựa trên nền tảng C ++,

bao gồm C #, D, Java và các phiên bản mới hơn của C.

7


2.1.3. Sự khác nhau cơ bản giữa C và C++
- Hầu hết IT trên thế giới đều biết về 2 ngơn ngữ lập trình C, C++. Như
chúng ta đã biết, C++ là ngôn ngữ ra đời sau ngôn ngữ C, thực chất nó mở
rộng cho ngơ ngữ C nhằm tăng cường tính an tồn, cung cấp cho các lập trình
viên nhiều lựa chọn hơn, đơn giản hóa lập trình ở mức cao hơn, và cung cấp
một cách tiếp cận tốt hơn đối với những chương trình có quy mơ lớn.
- C++ cũng là ngơn ngữ lớn hơn với nhiều tính năng và phức tạp hơn so
với C. Giữa C và C++ có rất nhiều khác biệt.
Bảng 2.1 So sánh sự khác nhau giữa C và C++
C

C++

Không phải ngôn ngữ hướng đối

Là một ngôn ngữ hướng đối tượng (gồm 4

tượng.

khái niệm về hướng đối tượng)

Là một ngơn ngữ lập trình thủ tục.

Khơng phải là ngơn ngữ lập trình thủ tục.


Chỉ hỗ trợ các structure.

Hỗ trợ các lớp và đối tượng.

Khơng có biến tham chiếu, chỉ hỗ

Hỗ trợ cả biến tham chiếu và con trỏ.

trợ con trỏ.
Không thể khai báo hàm trong các

Có thể khai báo hàm trong các structure.

structure.
Được xem là một ngôn ngữ lập

Được xem là sự kết hợp giữa ngơn ngữ lập

trình cấp thấp.

trình cấp thấp và cấp cao.

Khơng hỗ trợ các hàm inline, thay

Hỗ trợ các hàm inline.

vào đó có thể sử dụng khai
báo#define
Sử dụng phương pháp tiếp cận từ


Sử dụng phương pháp tiếp cận từ dưới

trên xuống (top-down).

lên (bottom-up).

Là ngơn ngữ lập trình hướng chức

Là ngơn ngữ lập trình hướng đối tượng

năng (function driven).

(Object driven).

8


2.2. Đa luồng (Multithread) trong C++
2.2.1. Khái niệm đa luồng
Đa luồng (Multithreading) là một form chuyên dụng của đa nhiệm
(multitasking) và một đa nhiệm là tính năng cho phép máy tính chạy hai hoặc
nhiều chương trình đồng thời. Nói chung, có hai kiểu đa nhiệm là: processbased và thread-based tương ứng: dựa trên tiến trình và dựa trên luồng.
Đa nhiệm dựa trên tiến trình xử lý việc thực thi đồng thời của các chương
trình. Đa nhiệm dựa trên luồng xử lý việc thực thi đồng thời các phần của
cùng một chương trình.
Một chương trình đa luồng chứa hai hoặc nhiều phần mà có thể chạy đồng
thời. Mỗi phần của chương trình đó được gọi là một thread, và mỗi thread
định nghĩa một path riêng biệt của sự thực thi.
C++ không chứa bất kỳ hỗ trợ có sẵn nào cho các ứng dụng đa luồng.
Thay vào đó, nó dựa hồn tồn vào hệ điều hành để cung cấp tính năng này.

2.2.2. Đa luồng trong C++
Tạo Thread trong C++
Đây là chương trình chúng ta sử dụng để tạo một POSIX thread:
#include
pthread_create(thread, attr, start_routine, arg)
Ở đây, pthread_create tạo một thread mới và làm nó có thể thực thi.
Chương trình này có thể được gọi bất cứ thời điểm nào ở bất cứ đâu trong
code của bạn. Dưới đây là miêu tả các tham số:
Tham số
thread
attr

Miêu tả
Một định danh duy nhất cho thread mới được trả về bởi
chương trình con
Một thuộc tính mà có thể được sử dụng để thiết lập các thuộc
tính của thread. Bạn có thể xác định một đối tượng thuộc
tính thread, hoặc NULL cho các giá trị mặc định

9


Chương trình C++ mà thread này sẽ thực thi một khi nó
được tạo
arg
Một tham số đơn mà có thể được truyền tới start_routine. Nó
phải được truyền bởi tham chiếu dạng một con trỏ của kiểu
void. NULL có thể được sử dụng nếu khơng có tham số nào
được truyền
Số thread tối đa có thể được tạo bởi một tiến trình là phụ thuộc vào trình

start_routine

triển khai (Implementation). Một khi được tạo, các thread là ngang hàng, và
có thể tạo các thread khác. Khơng có sự phụ thuộc giữa các thread trong C++.
Kết thúc Thread trong C++
Chương trình sau được sử dụng để kết thúc một POSIX thread trong C++:
#include
pthread_exit (status)
Ở đây pthread_exit được sử dụng để kết thúc một thread. Chương trình
pthread_exit() được gọi sau khi một thread đã hồn thành cơng việc của nó và
khơng cần thiết phải tồn tại nữa.
Nếu main() kết thúc trước các thread nó đã tạo, và kết thúc chương
trình pthread_create(), thì các thread khác sẽ tiếp tục thực thi. Nếu khơng thì,
chúng sẽ tự động được kết thúc khi main() hoàn thành.
2.3. Các phương pháp hiện có phát hiện bẫy lỗi khi chuyển mã code
C và C ++ sang Windows 64 bit
Có nhiều cách tiếp cận khác nhau để cung cấp tính chính xác của code
ứng dụng, một số có thể được tự động hóa và một số khác thì khơng thể. Một
số phương pháp đó là xử lý thủ cơng, check code tự động, kiểm tra hộp trắng,
kiểm tra thủ công, vv... Phân tích code tĩnh và kiểm thử hộp đen là các
phương pháp có thể được tự động hóa. Chúng ta sẽ kãy kiểm tra các phương
pháp này một cách chi tiết.

10


2.3.1. Review code
Cách tìm kiếm lỗi lâu đời nhất, được phê duyệt và đáng tin cậy nhất là
xem xét mã code. Phương pháp này dựa trên việc đọc nhóm mã code với việc
quan sát một số quy tắc và khuyến nghị. Tuy nhiên, phương pháp này không

thể được sử dụng để thử nghiệm rộng rãi các hệ thống chương trình hiện đại
vì kích thước lớn. Mặc dù phương pháp này cung cấp kết quả tốt nhất nhưng
nó khơng phải lúc nào cũng được sử dụng trong các hoàn cảnh của vịng đời
phát triển phần mềm hiện đại, trong đó việc phát triển và phát hành sản phẩm
ứng dụng là một yếu tố rất quan trọng. Đó là lý do tại sao phương pháp xem
xét mã code ít được sử dụng, thà rằng người ta cho nhân viên tuy ít kinh
nghiệm viết lại tồn bộ code có khi lại chất lượng hơn là việc xem xét các
module chương trình. Tuy nhiên đây lại là phương pháp nâng cao trình độ kỹ
năng của lập trình viên rất tốt nhưng nó khơng thể được coi là một phương
tiện đảm bảo chất lượng đầy đủ.
2.3.2. Phân tích code tĩnh
Các trình phân tích code tĩnh giúp các nhà phát triển nhận ra sự cần thiết
của việc review code thơng thường nhưng khơng có đủ thời gian cho việc đó.
Mục đích chính của họ là giảm số lượng code cần được kiểm tra bởi một lập
trình viên và do đó giảm thời gian xem xét. Các trình phân tích code tĩnh là
một lớp lớn các chương trình, được triển khai cho các ngơn ngữ lập trình khác
nhau và có một bộ các hàm khác nhau -  từ sự kết hợp code đơn giản để phân
tích các đoạn code nguy hiểm tiềm năng. Sử dụng hệ thống phân tích tĩnh cho
phép cải thiện đáng kể chất lượng code và tìm ra nhiều lỗi. Phương pháp phân
tích tĩnh được sử dụng khá nhiều và có nhiều tài liệu về cách tiếp cận này. Ưu
điểm của phương pháp này là nó có thể được sử dụng mà khơng tính đến độ
phức tạp và kích thước của giải pháp chương trình được phát triển.

11


2.3.3. Phân tích code động
Phân tích code động là phân tích phần mềm được cung cấp trong khi thực
thi các chương trình trên một bộ xử lý thực hoặc là ảo. Phân tích động thường
được hiểu là kiểm tra mã chương trình nhằm tối ưu hóa nó.

Phân tích động khơng cho phép tìm kiếm nhiều lỗi và thường khơng thể
thực thi tồn bộ mã chương trình, hoặc trình tự thực thi của nó khác rất nhiều
so với hệ thống thực. Bên cạnh đó, phân tích động gây ra gánh nặng tính tốn
trong q trình thực thi. Đó là lý do tại sao việc thu thập thơng tin lược tả tồn
diện (thường là tính tốn độ phức tạp) thường bị trì hỗn cho đến khi kết thúc
q trình thực thi chương trình được lược tả. Tất cả điều này làm cho phương
pháp này ít được sử dụng đặc biệt trong trường hợp cần thử nghiệm một ứng
dụng có kích thước dữ liệu lớn.
2.3.4. Phương pháp hộp trắng
Phương pháp kiểm tra hộp trắng là thực thi số lượng tối đa các nhánh
code có thể truy cập với sự trợ giúp của trình gỡ rối hoặc các phương tiện
khác. Phương pháp này đôi khi cũng được hiểu là một lỗi đơn giản để tìm ra
một lỗi nhất định. Việc kiểm tra tồn bộ mã chương trình bằng cách sử dụng
phương pháp hộp trắng đã là khơng thể do kích thước khổng lồ của các
chương trình code hiện đại. Ngày nay, phương pháp kiểm tra hộp trắng thuận
tiện ở bước khi tìm thấy lỗi và bạn nên tìm ra nguyên nhân gây ra lỗi đó.
Phương pháp thử nghiệm hộp trắng có đối thủ của nó để phủ nhận hiệu quả là
việc gỡ lỗi các chương trình thời gian thực. Lý do chính là khả năng xem
chương trình làm việc và đồng thời thay đổi trong đó, là một cách tiếp cận
khơng thể áp dụng được trong lập trình dựa trên số lượng sửa đổi mã lớn bằng
phương pháp 'cắt và thử'. Phương pháp thử nghiệm hộp trắng trong mọi
trường hợp là một cách rất tốn kém để cải thiện chất lượng của các hệ thống
chương trình lớn và phức tạp.

12


2.3.5. Phương pháp hộp đen
Phương pháp hộp đen có tiếng tốt hơn. Kiểm thử đơn vị cũng có thể được
coi là kiểm thử hộp đen. Ý tưởng chính của phương pháp này bao gồm viết

một tập các đoạn kiểm tra cho các module và chức năng riêng biệt, kiểm tra
tất cả các chế độ chính của cơng việc của nó. Một số nguồn tham khảo kiểm
thử đơn vị cho phương pháp hộp trắng vì nó dựa trên sự quen thuộc với cấu
trúc chương trình. Nhưng các chức năng và module khơng nên được coi là
hộp đen bởi vì các kiểm thử đơn vị khơng nên tính đến tổ chức bên trong của
một hàm. Lập luận cho phương pháp này là phương pháp phát triển khi các
đoạn kiểm tra được phát triển trước khi viết các hàm. Điều này cải thiện sự
kiểm sốt của chức năng của nó từ quan điểm đặc điểm kỹ thuật.
Kiểm thử đơn vị đã được đánh giá tốt trong quá trình phát triển các dự án
đơn giản cũng như các dự án phức tạp. Một trong những ưu điểm của kiểm
thử đơn vị là có thể kiểm tra tính chính xác của các thay đổi được thực hiện
trong chương trình ngay lập tức trong quá trình phát triển. Các lập trình viên
cố gắng làm như vậy cho tất cả các đoạn kiểm tra mất một vài phút để thực
hiện sửa chữa trong code, có thể nhận thấy một lỗi ngay lập tức và sửa chữa
nó.
2.3.6. Phương pháp kiểm thử thủ cơng
Đây có lẽ là bước cuối cùng của bất kỳ sự phát triển nào nhưng nó không
được coi là một phương pháp tốt và đáng tin cậy. Kiểm tra thủ cơng được tồn
tại vì khơng thể phát hiện tất cả các lỗi trong chế độ tự động hoặc thông qua
việc review code. Nhưng nếu một chương trình có chất lượng thấp và có
nhiều khiếm khuyết bên trong, việc kiểm tra và sửa lỗi của nó có thể mất
nhiều thời gian và vẫn không thể cung cấp chất lượng phù hợp của chương
trình. Phương pháp duy nhất để có được một chương trình chất lượng là chất

13


lượng code. Đó là lý do tại sao chúng ta sẽ không kiểm tra thủ công một là
một phương pháp đầy đủ trong quá trình phát triển các dự án lớn.
2.4. Nguyên nhân và phương pháp chi tiết

2.4.1. Tắt cảnh báo (Disabled warnings)
Tất cả các cuốn sách về phát triển phần mềm đều khuyên lập trình viên
nên đặt cấp độ cảnh báo cao nhất có thể trong trình biên dịch. Nhưng có
những tình huống trong thực tiễn khi mức chẩn đoán đối với một số bộ phận
dự án thấp hơn hoặc chẩn đốn thậm chí có thể bị vơ hiệu hóa hồn tồn. Do
đó, có thể bỏ lỡ những cảnh báo nghiêm trọng do trình biên dịch tạo ra trong
khi chuyển các chương trình trên hệ thống 64-bit mới.
Trong khi chuyển một ứng dụng, bạn nên luôn luôn bật cảnh báo cho
toàn bộ dự án. Điều này sẽ giúp bạn kiểm tra tính tương thích của mã và phân
tích mã kỹ lưỡng. Cách tiếp cận này có thể giúp bạn tiết kiệm rất nhiều thời
gian trong khi gỡ lỗi dự án về kiến trúc mới. Đây là một ví dụ đơn giản về
tràn bộ nhớ xảy ra trong một chương trình 64-bit nếu chúng ta bỏ qua các
cảnh báo.
unsigned char *array[50];
unsigned char size = sizeof(array);
32-bit system: sizeof(array) = 200
64-bit system: sizeof(array) = 400
2.4.2. Sử dụng những hàm với số lượng của những đối số thay đổi
Ví dụ điển hình là sử dụng khơng chính xác các hàm printf, scanf và các
biến thể của chúng:
 const char *invalidFormat = "%u";
size_t value = SIZE_MAX;
printf(invalidFormat, value);
 char buf[9];

14


sprintf(buf, "%p", pointer);
Trong trường hợp đầu tiên, nhà phát triển đã khơng tính đến kích thước

size_t khơng tương đương với kiểu unsigned trên nền tảng 64-bit. Do đó, nó
sẽ gây ra in sai kết quả nếu giá trị > UINT_MAX.
Trong trường hợp thứ hai, nhà phát triển đã khơng tính đến kích thước con
trỏ sẽ lơn hơn 32-bit trong tương lai. Do kết quả mã này sẽ gây tràn bộ đệm
trên kiến trúc 64-bit.
Không đúng chức năng sử dụng với một số lượng các đối số thay đổi, là
một lỗi điển hình trên tất cả các kiến trúc, khơng chỉ trên 64-bit. Điều này liên
quan đến nguy cơ cơ bản của việc sử dụng các cấu trúc ngôn ngữ C ++. Thực
tiễn chung là từ chối chúng và sử dụng các phương pháp lập trình an tồn
hơn.
Với ví dụ trên phương pháp an tồn hơn đó là có thể thay thế printf băng
cout và sprintf với boost :: format hoặc std :: stringstream . Nếu bắt buộc sử
dụng chức năng của kiểu scanf thì nên sử dụng các macro đặc biệt ví dụ
// PR_SIZET on Win64 = "I"
// PR_SIZET on Win32 = ""
// PR_SIZET on Linux64 = "l"
// ...
size_t u; scanf("%" PR_SIZET "u", &u);
2.4.3. Số ma thuật
Mã chất lượng kém thường chứa số ma thuật, sự hiện diện của nó là nguy
hiểm. Trong quá trình di chuyển mã lên nền tảng 64-bit, những con số ma
thuật này có thể làm cho mã khơng hiệu quả nếu chúng tham gia tính tốn địa
chỉ, kích thước đối tượng hoặc các tốn tử bit.

15


Giá trị

Miêu tả


4

Số byte của con trỏ

32

Số bit của con trỏ

0x7FFFFFFF

Giá trị lớn nhất biến được chọn trong 32bit. Mặt nạ bit cao được
đưa về 0 trong 32-bit

0x80000000

Giá trị nhỏ nhất biến được chọn trong 32bit. Mặt nạ bit cao nhất
là 1 còn lại là 0 trong 32-bit

0xFFFFFFFF

Giá trị lớn nhất của biến 32 bit. Một bản ghi khác -1 như một
dấu hiệu lỗi.

Bảng 2.2: Số ma thuật cơ bản có thể gây nguy hiểm trong q trình
dịch chuyển ứng dụng từ nền tảng 32-bit lên 64-bit.
Chúng ta hãy xem xét một số lỗi liên quan đến việc sử dụng số ma thuật.
Thường xuyên nhất là sử dụng số để lưu trữ loại kích thước.
1) size_t ArraySize = N * 4;
intptr_t *Array = (intptr_t *)malloc(ArraySize);

2) size_t values[ARRAY_SIZE];
memset(values, 0, ARRAY_SIZE * 4);
3) size_t n, newexp;
n = n >> (32 - newexp);
Giả sử rằng trong mọi trường hợp kích thước của các kiểu được sử dụng
luôn là 4 byte. Để làm cho mã đúng, chúng ta nên sử dụng toán tử sizeof ().
1) size_t ArraySize = N * sizeof(intptr_t);
intptr_t *Array = (intptr_t *)malloc(ArraySize);
2) size_t values[ARRAY_SIZE];
memset(values, 0, ARRAY_SIZE * sizeof(size_t));

16


3) size_t n, newexp;
n = n >> (CHAR_BIT * sizeof(n) - newexp);
2.4.4. Lưu trữ số nguyên trong kiểu double
Kiểu double như là một quy tắc, có kích thước 64bit và tương thích với
chuẩn IEEE-754 trên các hệ thống 32-bit và 64-bit. Một số lập trình viên sử
dụng kiểu double để lưu trữ và làm việc với các loại số nguyên.
size_t a = size_t(-1);
double b = a;
--a;
--b;
size_t c = b; // x86: a == c // x64: a != c
Ví dụ được đưa ra có thể được chứng minh trên một hệ thống 32-bit, vì
kiểu double có 52 bit quan trọng và có khả năng lưu trữ một giá trị số nguyên
32-bit mà không bị mất. Nhưng trong khi cố gắng để lưu trữ một số nguyên
64-bit trong double thì giá trị có thể bị mất (xem hình ).


Hình 2.1 Lưu trữ số nguyên trong kiểu double
Một giá trị gần đúng có thể được sử dụng trong chương trình của bạn,
nhưng để an tồn, chúng tơi muốn cảnh báo bạn về các hiệu ứng có thể có đối
với kiến trúc mới. Trong mọi trường hợp, không nên trộn số học với số học
dấu phẩy động.

17


2.4.5. Phép toán dịch bit
Các thao tác chuyển đổi bit có thể gây ra rất nhiều sự cố trong quá trình
chuyển từ hệ thống 32-bit sang 64-bit nếu khơng quan tâm đúng mức. Chúng
ta hãy bắt đầu bằng một ví dụ về một hàm được định nghĩa bit bạn chọn sẽ
được coi như là một biến của kích thước bộ nhớ
ptrdiff_t SetBitN(ptrdiff_t value, unsigned bitNum){
ptrdiff_t mask = 1 << bitNum;
return value | mask; }
Đoạn mã trên chỉ hoạt động trên kiến trúc 32-bit và cho phép định nghĩa
các bit từ 0 đến 31. Sau khi chương trình được chuyển lên nền tảng 64- bit,
cần phải xác định các bit từ 0 – 63

Hình 2.2 Tính tốn giá trị Mask
Để sửa mã, cần phải làm cho "1" không đổi, cùng kiểu với mặt nạ biến.
ptrdiff_t mask = ptrdiff_t(1) << bitNum;
Thêm một câu hỏi nữa. Kết quả của hàm chưa sửa chữa SetBitN(0,31) là
hướng tới sẽ là gì? Câu trả lời đúng là 0xffffffff80000000. Kết quả của biểu
thức 1 << 31 là số âm -2147483648. Số này được tạo thành trong một biến số
nguyên 64 bit như 0xffffffff80000000. Bạn nên nhớ và lưu ý tới những ảnh
hưởng của việc chuyển đổi các giá trị của các Kiểu khác nhau. Để hiểu rõ hơn


18


×