30/09/2018, 16:04

Cùng đặt vấn đề nào (Bản số 9)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct
{
     char x;
     int y;
}mystruct;

int main (){
     printf("%d", sizeof(mystruct));
}

Cái gì sẽ được in ra, giải thích?

Nguyễn Minh Dũng viết 18:14 ngày 30/09/2018
8

In ra kích thước của mystruct. Nếu tính bằng tay mình sẽ thấy nó có 5 bytes à. Nhưng thực tế khi mình in ra trong bài này nó phải ra tới 8 bytes. Cái này gọi là struct padding, nó làm tròn cái char x từ 1 byte lên tới 4 bytes. Cái này hình như để tăng tốc độ truy vấn, để anh tìm hiểu lại và xác nhận cái này.

Tuy nhiên cũng có một cách để nó in ra đúng 5 bytes đó là mình bắt buộc nó phải đóng gói đúng như vậy

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct __attribute__((__packed__))
{
     int y;
     char x;
}mystruct;

int main (){
     printf("%d", sizeof(mystruct));
}
Dũng Kon viết 18:20 ngày 30/09/2018

Thế mà giờ em mới biết có cái kiểu đóng gói này đấy, dùng oke với C và C++ luôn phải không anh?

Dũng Kon viết 18:10 ngày 30/09/2018

Mà không hình như có lỗi rồi anh à, sao em chạy ko được

Nguyễn Minh Dũng viết 18:05 ngày 30/09/2018

Anh không nhớ rõ nó là C hay C++, em thử với C++ xem. Anh đang chạy với C++.

ddhuy137 viết 18:04 ngày 30/09/2018

Có vài cách để mình pack struct lại ko cho padding:

  1. Trên Visual studio mình dùng macro như sau hoặc edit lại trong configuration của project.
#pragma pack(push) // begin of struct
#pragma pack(1)
...
#pragma pop // end of struct
  1. Dùng marco như sau:
struct MyPackedData
{
    char Data1;
    long Data2 __attribute__((packed));
    char Data3;
};
Nguyễn Minh Dũng viết 18:17 ngày 30/09/2018

cái #pragma pack(1) là để thông báo cho compiler biết là mức đóng gói là 1 byte đúng không Vậy mặc định chắc phải là 2 hoặc 4 bytes rồi.

long Data2 __attribute__((packed)); Cái này là sao, sao mình lại pack cái thằng bự nhất?

ddhuy137 viết 18:12 ngày 30/09/2018

Đúng rồi a Đạt, pack(1) là để thông báo mình đóng gói mức 1 bytes.
Mình pack thằng bư nhất, vì nó là thằng gây ra padding cho mấy thằng còn lại :D. Nó sẽ có 3 bytes nằm cùng 1 WORD với thằng char Data1 và 1 byte nằm cùng 1 WORD với thằng Data2

Nguyễn Minh Dũng viết 18:10 ngày 30/09/2018

À, cứ nắm đầu thằng bự nhất, rồi kêu nó phải tự đi tìm mấy thằng khác để chia sẻ vùng nhớ à. Thế anh thấy viết như vầy dễ xài hơn. Cái này copy trên mạng hehe

typedef struct __attribute__((__packed__))
{
     int y;
     char x;
}mystruct;
Dũng Kon viết 18:14 ngày 30/09/2018

hay quá, có thêm skills mới rồi

ddhuy137 viết 18:19 ngày 30/09/2018

Chạy thử đoạn code sau mình sẽ thấy rõ hơn

typedef struct __attribute__((__packed__))
{
    char c1;
    int i;
    char c2;
} mstruct;

int main(int argc, char** argv)
{
    mstruct t;
    printf("size %ld\n", sizeof(t));
    printf("%x %x %x\n", &t.c1, &t.i, &t.c2);

    return 0;
}

Result: ta thấy giữa biến c1 và biến i chỉ cách nhau 1 offset -> ko có padding giữa 2 biến này, tức là biến i bị chia ra nằm trên 2 WORD khác nhau

size 6
6cd847f0 6cd847f1 6cd847f5
Nguyễn Minh Dũng viết 18:18 ngày 30/09/2018

6cd847f0 6cd847f1 6cd847f5

Hay, cái này mới thấy lần đầu à nha. Địa chỉ bị chia ra. Làm như thế này sẽ tiết kiệm được bộ nhớ. Mà hình như nó sẽ truy xuất chậm hơn đúng không @ddhuy137

ddhuy137 viết 18:17 ngày 30/09/2018

nó sẽ tốn hơn 2 cycle của CPU để đọc thằng này ra. Vì 1 lần CPU chỉ đọc 1 WORD thôi, nhưng trong TH này phải đọc 2 WORD rồi còn phải shift left, shift right, and, or gì đó nữa để ra đúng value
Khi gán lại cũng vậy

Dũng Kon viết 18:17 ngày 30/09/2018

Cao siêu quá @ddhuy137

ddhuy137 viết 18:10 ngày 30/09/2018

Như bài toán sau đây, mình sẽ không thấy có padding, nhưng nên cẩn thận nó vẫn được aligment trên memory đó.
Size luôn là 6 bytes, thông thường ta sẽ nghĩ là 2 biến st1 & st2 cách nhau 6 offset (đúng bằng sizeof của struct này) trên memory
Nhưng thưc tế địa chỉ của 2 biến st1 & st2 sẽ cách nhau:
6bytes: nếu là system 16bit / 32 bit
16bytes: nếu là system 64bit

typedef struct
{
short s1;
short s2;
short s3;
} mstruct;

int main(int argc, char** argv)
{
mstruct st1, st2;
printf ("Size: %ld\n", sizeof(mstruct));
printf ("st1: %x st2: %x\n", &st1, &st2);
return 0;
}

Result:

Size: 6
st1: ec8a2e50 st2: ec8a2e60
Nguyễn Minh Dũng viết 18:12 ngày 30/09/2018

Vậy có nghĩa là dữ liệu bị đứt quãng, anh hiểu vậy có đúng không Huy?

ddhuy137 viết 18:11 ngày 30/09/2018

Dữ liệu vẫn nằm liên mạch anh :). Nhưng nó sẽ padding & alignment trên memory vì CPU của mình 1 lần sẽ đọc 1 WORD lên, nên nếu để 2 biến này hoàn toàn trên 2 WORD khác nhau thì sẽ đọc & xử lý nhanh hơn.

Nguyễn Minh Dũng viết 18:19 ngày 30/09/2018

Nhưng theo anh biết 1 WORD có 4 bytes chứ mấy, làm sao đọc được 1 phát 6 bytes giờ?

Đúng là anh thấy 2 thằng này cách nhau tới 16 offset (0x60-0x50). Nhưng mà ở đây mình nói là DWORD mới đúng. Vì DWORD = 8 Bytes.

ddhuy137 viết 18:09 ngày 30/09/2018

Sorry anh, e compile trên system 64bit nên WORD của nó bằng 8bytes :D.
Anh Dat & @Dung_Kon thử compile trên 32bit xem.

Nguyễn Minh Dũng viết 18:11 ngày 30/09/2018
#include <stdio.h>

typedef struct
{
    short s1;
    short s2;
    short s3;
} mstruct;

int main(int argc, char** argv)
{
    mstruct st1, st2;
    printf ("Size: %ld\n", sizeof(mstruct));
    printf ("st1: %x st2: %x\n", &st1, &st2);
    return 0;
}

Result:

Size: 6
st1: 28ff1a st2: 28ff14

Em thử giải thích cái này thử xem, cái này a bị khó hiểu rồi à nha

ddhuy137 viết 18:06 ngày 30/09/2018

Đã tìm được lý do
Chính xác là do compiler. Như vậy việc runtime alignment là tùy thuộc vào compiler và các option của nó.

Compile with cl compiler (x86):

cl /EHsc /nologo /W4 test.c
Size: 6
st1:302ff04 st2:302ff0c
Bài liên quan
0