Category Archives: C/C++

OpenMP

OpenMP (Open Multi-Processing)

OpenMP là một API hỗ trợ lập trình song song (ngoài ra bạn có thể tìm hiểu về CUDA của NVIDIA). OpenMP bao gồm nhiều chỉ thị chạy khác nhau. API này đã có sẵn trong Visual Studio C/C++. Vì thế khi sử dụng bạn chỉ cần import dòng này vào:

#include <omp.h>

là xong!

Bạn thử chạy dòng code này:

  
#include <stdio.h>
#include <omp.h>
#include <conio.h> # Để có thể sử dụng lệnh _getch() (dừng màn hình)

int main()
{
    #pragma omp parallel
    {
        printf("Hello world!\n");
    }

    _getch();
    return 0;
}

Sau khi chạy xong đoạn chương trình trên bạn có lẽ CHỈ THẤY 1 dòng Hello world! được xuất ra! Có lẽ bạn sẽ nói: “Như vậy mà gọi là SONG SONG ah?!? Có khác gì dòng code bình thường đâu?!?”.
Vấn đề là ở đây! Nếu bạn đã thiết lập chạy song song thì sẽ in ra 4 dòng Hello world! (Còn tùy vào máy của bạn có mấy nhân! Bạn có thể biết được số nhân của máy qua dòng lệnh sau:

printf("Số nhân: %d\n", omp_get_max_threads());

)
Tuy bạn có thể có 4 nhân nhưng lại không in ra được màn hình 4 dòng Hello world! là do bạn chưa enable cho nó!

Để Enable OpenMP bạn làm như sau:
* Phải chuột lên project của bạn, chọn properties.
* C/C++ -> Language, và thay đổi “OpenMP Support” thành ‘Yes’.
* Click OK!

Xong! Bạn có thể Ctrl+F5 lại xem!!! Nếu vẫn chỉ có 1 dòng được in ra thì máy của bạn chỉ hỗ trợ có 1 nhân thôi!

Bạn có thể tham khảo thêm tại đây

Advertisements

Toán tử sizeof với struct (C)

Toán tử sizeof hay được dùng để tính kích thức bộ nhớ cần khi cấp phát động. Để diễn tả một cách dễ hiểu thì ví dụ là hay nhất!!!

struct HS {

        char name;

        int id;

};

Bạn đoán thử sizeof(HS) = ???

Có lẽ bạn sẽ đoán nó = 5 (sizeof(char) = 1, sizeof(int) = 4). Nhưng trình biên dịch lại cho kết quả là 8??? Vì sao vậy?

Cơ chế cấp phát là như sau:

  • Đầu tiên nó sẽ lấy kích thước của biến lớn nhất trong struct HS là sizeof(id) = 4 cấp phát 4 byte để chứa biến name (kích thước = 1 byte) còn dư 3 byte.
  • Tiếp theo nó sẽ chứa tiếp biến id (kích thước 4 byte) mà chỉ còn có 3 byte (không đủ) nên bộ nhớ sẽ cấp phát thêm 4 byte để chứa biến id.
  • => Sau khi cấp phát: 8 byte (còn dư 3 byte) !!!

Đến đây chắc bạn có câu trả lời rồi nhỉ? ^^ . Để hiểu thêm ta có 1 ví dụ típ theo như sau:

struct SV {

        int DiemAV;

        double diemToan;

        double diemLT;

        int diemRL;

};

Bạn đoán xem sizeof(SV) = ???

Đáp án là 32 byte!!! (dư 8 byte). Vậy phải làm sao để tiết kiệm được 8 byte này???

Đơn giản nhất là sắp các biến trong struct SV lại thôi!!!

struct SV {

        int DiemAV;

        int diemRL;

        double diemToan;

         double diemLT;

};

Và bây giờ  sizeof(SV) = 24 byte !!! ^^

struct SV { };

Now, sizeof(SV) = ???

=> 1 byte!!!

Mặc định sẽ dành 1 byte để chứa struct!!!

Con trỏ trong C/C++

Con trỏ thực sự là một biến giữ địa chỉ. Tuy nhiên nó ẩn chứa rất nhiều “bí ẩn”!?! Ta cùng xem ví dụ sau:

01. short a = 5;
02. short* pshort;       // Khai báo một con trỏ kiểu short
03. unsigned long* plong; // Khai báo một con trỏ kiểu unsigned long
04. pshort = &a;          // Con trỏ pshort đang giữ địa chỉ của a
05. *pshort = 7;          // Dòng này đã thay đổi giá trị của a (bây giờ a = 7)
06. unsigned short A[5] = {0}; // Khai báo mảng A với năm phần tử
07. plong = (unsigned long*)&A[3];
08. *plong = 0xDEADBEEF;   // A[3] = 0xBEEF ~ 48879; A[4] = 0xDEAD ~ 57005;
/*
Dòng code 7, 8 khá đặc biệt! kiểu long có kích thước 4 byte mà kiểu short có kích thước 2 byte nên *plong tương đương một biến 4 byte bao trùm lên cả A[3] và A[4]
*/

Qua những dòng code trên bạn chắc cũng đã phần nào hiểu sơ về con trỏ!!! ^^

VOID
Bạn để ý thấy hầu như các con trỏ trên đều có kiểu cụ thể (int, short,…) Thế thì liệu có con trỏ nào khác mà không thuộc những kiểu dữ liệu ấy không?!? Xin thưa là có! Đó là con trỏ vô kiểu 🙂 (không thuộc kiểu nào hết ak!)

void*

Bạn nghĩ biến A[3] ngoài kiểu unsigned short còn có kiểu nào nữa không??? 🙂 Đó là kiểu void!!! Có thể bạn sẽ khá bất ngờ về điều này!

Biến con trỏ void có thể nhận địa chỉ của bất kì biến nào, có thể được gán bằng biến con trỏ tùy ý. Dựa vào đặc tính này bạn có thể viết những đoạn mã nguồn tổng quát có thể dùng lại cho nhiều kiểu dữ liệu khác nhau.

Bản thân kiểu void không xác định kích thước!!! Bạn nghĩ sizeof(void) = ??? 🙂 VÔ NGHĨA! Nhưng sizeof(void*) = 4 (Tùy vào công nghệ). Vì sao vậy? Đó là do con trỏ void* giữ địa chỉ bộ nhớ.
Ví dụ:

float a[5];
void *pvoid = a;
...

pvoid + 1, p[1], *p => Error!!!
Mọi thao tác đều phải ép kiểu!
*p => Phải là: *(float*)p

CON TRỎ HẰNG

Con trỏ trỏ đến vùng dữ liệu hằng

Vùng dữ liệu mà con trỏ đang trỏ đến không được phép sửa đổi nhưng bản thân biến con trỏ thì có thể thay đổi (có thể trỏ đến nơi khác, tăng giảm địa chỉ).

const char* pStr = “I love you…<3”;
// <=> char const* pStr = “I love you…<3”;
pStr[0] = ‘i’;             // Error! => Dữ liệu không thể đổi
pStr++;                      // OK
pStr = “I miss you!”;   // OK
//——————————-
const char* pChr;
char cStr[17] = “Have a nice day!”;
cStr[0] = ‘h’;        // OK
pChr = cStr;
pChr[0] = ‘H’;     // Error! => Ko thể thay đổi

Hằng con trỏ

Bản thân con trỏ bị gim vào 1 địa chỉ bộ nhớ và không thể trỏ đến vùng dữ liệu mới hay tăng giảm con trỏ. Điểm đặc biệt là vùng dữ liệu có thể thay đổi thông qua con trỏ.

char* const cp;
char cStr[] = "Have a nice day!";
cp = cStr;
cp[0] = 'h';    // OK
cp = "Have a nice night!";  // Error!
cp++;  // Error!

Hằng con trỏ đến vùng dữ liệu hằng

Đây là một con trỏ hằng. Nó không được phép thay đổi giá trị địa chỉ mà nó đang giữ và cũng không thể thay đổi vùng dữ liệu mà nó đang trỏ đến.

const char* const ccStr = "You are number one!";
// Chú ý có 2 chữ "const" gần nhau
ccStr[0] = 'y'; // => Error!
ccStr = "Good!"; // => Error!

Con trỏ hàm

Chức năng: giữ các hàm. Con trỏ hàm tạo điều kiện cho các Dev viết những đoạn code có khả năng tái sử dụng cao trong nhiều tình huống nhờ việc biến tấu các con trỏ hàm. Khi thay con trỏ hàm bằng những hàm cụ thể thì chương trình sẽ thực thi ứng với từng hàm cụ thể.
VD:

// Các hàm phép tính cơ bản
int Cong(int a, int b){ return a + b; }
int Tru(int a, int b){ return a - b; }
int Nhan(int a, int b){ return a * b; }
// Hàm chia lấy phần nguyên
int ChiaLayNguyen(int a, int b){ return a / b; }
// Hàm chia lấy phần dư
int ChiaLayDu(int a, int b) { return a % b; }
int TinhToan(int a, int b, int (*ConTroHam)(int, int))
{ return ConTroHam(a, b); }
//----------------------------------------------------
// MAIN
int main()
{
    // Sử dụng
    int a = 10, b = 3;
    cout << a << " + " << b << " = "
         << TinhToan(a, b, &Cong) << endl;

    cout << a << " - " << b << " = "
         << TinhToan(a, b, &Tru) << endl;

    cout << a << " * " << b << " = "
         << TinhToan(a, b, &Nhan) << endl;

    cout << a << " / " << b << " = "
         << TinhToan(a, b, &ChiaLayNguyen) << endl;

    cout << a << " % " << b << " = "
         << TinhToan(a, b, &ChiaLayDu) << endl;

    return 0;
}