Efek paket #pragma


233

Saya bertanya-tanya apakah seseorang dapat menjelaskan kepada saya apa yang dilakukan oleh #pragma packpernyataan preprosesor, dan yang lebih penting, mengapa seseorang ingin menggunakannya.

Saya memeriksa halaman MSDN , yang menawarkan beberapa wawasan, tetapi saya berharap untuk mendengar lebih banyak dari orang-orang yang berpengalaman. Saya sudah melihatnya dalam kode sebelumnya, meskipun saya tidak bisa menemukan tempat lagi.


1
Ini memaksa penyelarasan / pengemasan struct tertentu, tetapi seperti semua #pragmaarahan mereka didefinisikan implementasi.
dreamlax

A mod s = 0di mana A adalah alamat dan s adalah ukuran datatype; ini memeriksa apakah suatu data tidak selaras.
legends2k

Jawaban:


422

#pragma packmenginstruksikan kompiler untuk mengemas anggota struktur dengan penyelarasan tertentu. Sebagian besar kompiler, ketika Anda mendeklarasikan struct, akan menyisipkan bantalan di antara anggota untuk memastikan bahwa mereka disejajarkan dengan alamat yang sesuai dalam memori (biasanya kelipatan dari ukuran tipe). Ini menghindari penalti kinerja (atau kesalahan langsung) pada beberapa arsitektur yang terkait dengan mengakses variabel yang tidak selaras dengan benar. Misalnya, diberikan bilangan bulat 4-byte dan struct berikut:

struct Test
{
   char AA;
   int BB;
   char CC;
};

Kompiler dapat memilih untuk meletakkan struct di memori seperti ini:

|   1   |   2   |   3   |   4   |  

| AA(1) | pad.................. |
| BB(1) | BB(2) | BB(3) | BB(4) | 
| CC(1) | pad.................. |

dan sizeof(Test)akan menjadi 4 × 3 = 12, meskipun hanya berisi 6 byte data. Kasus penggunaan yang paling umum untuk #pragma(sepengetahuan saya) adalah ketika bekerja dengan perangkat perangkat keras di mana Anda perlu memastikan bahwa kompiler tidak memasukkan bantalan ke dalam data dan setiap anggota mengikuti yang sebelumnya. Dengan #pragma pack(1), struct di atas akan ditata seperti ini:

|   1   |

| AA(1) |
| BB(1) |
| BB(2) |
| BB(3) |
| BB(4) |
| CC(1) |

Dan sizeof(Test)akan menjadi 1 × 6 = 6.

Dengan #pragma pack(2), struct di atas akan ditata seperti ini:

|   1   |   2   | 

| AA(1) | pad.. |
| BB(1) | BB(2) |
| BB(3) | BB(4) |
| CC(1) | pad.. |

Dan sizeof(Test)akan menjadi 2 × 4 = 8.

Urutan variabel dalam struct juga penting. Dengan variabel yang dipesan seperti berikut:

struct Test
{
   char AA;
   char CC;
   int BB;
};

dan dengan #pragma pack(2), struct akan ditata seperti ini:

|   1   |   2   | 

| AA(1) | CC(1) |
| BB(1) | BB(2) |
| BB(3) | BB(4) |

dan sizeOf(Test)akan menjadi 3 × 2 = 6.


76
Mungkin ada baiknya menambahkan sisi buruk dari pengepakan. (Akses objek yang tidak selaras lambat dalam kasus terbaik , tetapi akan menyebabkan kesalahan pada beberapa platform.)
jalf

11
Tampaknya keselarasan "penalti kinerja" yang disebutkan sebenarnya bisa bermanfaat pada beberapa sistem danluu.com/3c-conflict .

6
@Pacerier Tidak juga. Posting itu berbicara tentang beberapa penyelarasan yang cukup ekstrim (menyelaraskan pada batas 4KB). CPU mengharapkan penyelarasan minimum tertentu untuk berbagai tipe data, tetapi yang membutuhkan, dalam kasus terburuk, perataan 8 byte (tidak termasuk tipe vektor yang mungkin memerlukan perataan 16 atau 32 byte). Tidak menyelaraskan pada batas-batas itu umumnya memberi Anda hit kinerja yang terlihat (karena beban mungkin harus dilakukan sebagai dua operasi, bukan satu), tetapi jenisnya baik selaras atau tidak. Penjajaran yang lebih ketat dari itu tidak memberi Anda apa pun (dan merusak pemanfaatan cache
jalf

6
Dengan kata lain, sebuah double berharap berada pada batas 8 byte. Meletakkannya pada batas 7 byte akan merusak kinerja. Tetapi meletakkannya pada batas 16, 32, 64 atau 4096 byte tidak memberi Anda apa pun di atas apa yang sudah diberikan batas 8 byte. Anda akan mendapatkan kinerja yang sama dari CPU, sementara mendapatkan pemanfaatan cache yang jauh lebih buruk karena alasan yang diuraikan dalam posting itu.
Jalf

4
Jadi pelajarannya bukanlah "pengemasan bermanfaat" (pengepakan melanggar penyelarasan alami tipe ', sehingga melukai kinerja), tetapi hanya "jangan menyelaraskan di luar apa yang diperlukan"
jalf

27

#pragmadigunakan untuk mengirim pesan non-portabel (seperti dalam kompiler ini saja) ke kompiler. Hal-hal seperti menonaktifkan peringatan dan pengemasan struct tertentu adalah alasan umum. Menonaktifkan peringatan spesifik sangat berguna jika Anda mengkompilasi dengan peringatan saat bendera kesalahan diaktifkan.

#pragma packkhusus digunakan untuk menunjukkan bahwa struct yang sedang dikemas tidak harus memiliki anggota yang disejajarkan. Ini berguna ketika Anda memiliki antarmuka yang dipetakan memori ke perangkat keras dan harus dapat mengontrol persis di mana anggota struct yang berbeda menunjuk. Ini terutama bukan optimasi kecepatan yang baik, karena sebagian besar mesin jauh lebih cepat dalam menangani data yang disejajarkan.


17
Untuk membatalkan setelahnya lakukan ini: #pragma pack (push, 1) dan #pragma pack (pop)
malhal

16

Ini memberitahu kompiler batas untuk menyelaraskan objek dalam suatu struktur. Misalnya, jika saya memiliki sesuatu seperti:

struct foo { 
    char a;
    int b;
};

Dengan mesin 32-bit yang khas, Anda biasanya "ingin" memiliki 3 byte padding di antara adan bsehingga bakan mendarat pada batas 4-byte untuk memaksimalkan kecepatan aksesnya (dan itulah yang biasanya akan terjadi secara default).

Namun, jika Anda harus mencocokkan struktur yang ditentukan secara eksternal, Anda ingin memastikan kompiler menjabarkan struktur Anda persis sesuai dengan definisi eksternal itu. Dalam hal ini, Anda dapat memberikan kompiler #pragma pack(1)untuk mengatakannya agar tidak menyisipkan bantalan di antara anggota - jika definisi struktur mencakup bantalan di antara anggota, Anda memasukkannya secara eksplisit (misalnya, biasanya dengan anggota bernama unusedNatau ignoreN, atau sesuatu di atasnya memesan).


"Anda biasanya" ingin "memiliki 3 byte padding antara a dan b sehingga b akan mendarat pada batas 4-byte untuk memaksimalkan kecepatan aksesnya" - bagaimana cara memiliki 3 byte padding memaksimalkan kecepatan akses?
Ashwin

8
@Ashwin: Menempatkan bpada batas 4-byte berarti prosesor dapat memuatnya dengan mengeluarkan beban 4-byte tunggal. Meskipun agak tergantung pada prosesor, jika berada pada batas ganjil, ada peluang bagus untuk memuatnya akan mengharuskan prosesor mengeluarkan dua instruksi pemuatan yang berbeda, kemudian gunakan shifter untuk menyatukan potongan-potongan tersebut. Penalti umum ada di urutan 3x lebih lambat dari item itu.
Jerry Coffin

... jika Anda melihat kode perakitan untuk membaca selaras dan tidak selaras int, selaras membaca biasanya mnemonik tunggal. Bacaan yang tidak selaras dapat terdiri dari 10 baris perakitan dengan mudah karena potongan-potongan int bersama-sama, mengambilnya byte demi byte dan menempatkannya pada lokasi register yang benar.
SF.

2
@ SF: Ini bisa - tetapi bahkan ketika tidak, jangan disesatkan - pada CPU x86 (untuk satu contoh jelas) operasi dilakukan dalam perangkat keras, tetapi Anda masih mendapatkan kira-kira rangkaian operasi yang sama dan perlambatan.
Jerry Coffin

8

Elemen data (misalnya anggota kelas dan struct) biasanya diselaraskan pada batas WORD atau DWORD untuk prosesor generasi saat ini untuk meningkatkan waktu akses. Mengambil DWORD pada alamat yang tidak dapat dibagi oleh 4 membutuhkan setidaknya satu siklus CPU tambahan pada prosesor 32 bit. Jadi, jika Anda memiliki misalnya tiga anggota char char a, b, c;, mereka sebenarnya cenderung mengambil 6 atau 12 byte penyimpanan.

#pragmamemungkinkan Anda untuk mengesampingkan ini untuk mencapai penggunaan ruang yang lebih efisien, dengan mengorbankan kecepatan akses, atau untuk konsistensi data yang tersimpan di antara berbagai target penyusun. Saya bersenang-senang dengan transisi dari 16 bit ke 32 bit kode; Saya berharap porting ke kode 64 bit akan menyebabkan jenis sakit kepala yang sama untuk beberapa kode.


Sebenarnya, char a,b,c;biasanya akan mengambil 3 atau 4 byte penyimpanan (setidaknya x86) - itu karena persyaratan penyelarasannya adalah 1 byte. Jika tidak, lalu bagaimana Anda akan menghadapinya char str[] = "foo";? Akses ke a charselalu berupa fetch-shift-mask yang sederhana, sementara akses ke a intbisa berupa fetch-fetch-merge atau hanya mengambil, tergantung pada apakah itu sejajar atau tidak. intmemiliki (pada x86) perataan 32-bit (4 byte) karena jika tidak, Anda akan mendapatkan (katakanlah) setengah intdalam satu DWORDdan setengah lainnya, dan itu akan membutuhkan dua pencarian.
Tim Čas

3

Compiler dapat menyelaraskan anggota dalam struktur untuk mencapai kinerja maksimum pada platform tertentu. #pragma packarahan memungkinkan Anda untuk mengontrol perataan itu. Biasanya Anda harus membiarkannya secara default untuk kinerja optimal. Jika Anda harus meneruskan struktur ke mesin jarak jauh yang biasanya Anda gunakan #pragma pack 1untuk mengecualikan penyelarasan yang tidak diinginkan.


2

Kompiler dapat menempatkan anggota struktur pada batas byte tertentu untuk alasan kinerja pada arsitektur tertentu. Ini dapat menyebabkan bantalan yang tidak digunakan di antara anggota. Packing struktur memaksa anggota untuk berdekatan.

Ini mungkin penting misalnya jika Anda memerlukan struktur agar sesuai dengan file atau format komunikasi tertentu di mana data yang Anda perlukan data berada pada posisi tertentu dalam suatu urutan. Namun penggunaan seperti itu tidak berurusan dengan masalah endian-ness, jadi meskipun digunakan, itu mungkin tidak portabel.

Mungkin juga untuk overlay struktur register internal dari beberapa perangkat I / O seperti pengontrol UART atau USB misalnya, agar akses register melalui struktur daripada alamat langsung.


1

Anda mungkin hanya ingin menggunakan ini jika Anda mengkodekan ke beberapa perangkat keras (misalnya perangkat yang dipetakan memori) yang memiliki persyaratan ketat untuk pemesanan register dan penyelarasan.

Namun, ini terlihat seperti alat yang cukup tumpul untuk mencapai tujuan itu. Pendekatan yang lebih baik adalah dengan kode driver mini di assembler dan memberinya antarmuka panggilan C daripada meraba-raba dengan pragma ini.


Saya sebenarnya menggunakannya cukup banyak untuk menghemat ruang dalam tabel besar yang tidak sering diakses. Di sana, itu hanya untuk menghemat ruang dan bukan untuk penyelarasan ketat. (Baru saja memilih Anda, btw. Seseorang memberi Anda suara negatif.)
Todd Lehman

1

Saya sudah menggunakannya dalam kode sebelumnya, meskipun hanya untuk antarmuka dengan kode lama. Ini adalah aplikasi Mac OS X Cocoa yang perlu memuat file preferensi dari versi Carbon sebelumnya (yang sendiri kompatibel dengan versi M68k System 6.5 yang asli ... Anda dapat idenya). File preferensi dalam versi asli adalah dump biner dari struktur konfigurasi, yang menggunakan#pragma pack(1) untuk menghindari mengambil ruang tambahan dan menghemat sampah (yaitu byte padding yang seharusnya berada dalam struktur).

Penulis asli kode juga digunakan #pragma pack(1)untuk menyimpan struktur yang digunakan sebagai pesan dalam komunikasi antar-proses. Saya pikir alasannya di sini adalah untuk menghindari kemungkinan ukuran padding yang tidak diketahui atau diubah, karena kode kadang-kadang melihat bagian tertentu dari struct pesan dengan menghitung sejumlah byte dari awal (ewww).


1

Saya telah melihat orang menggunakannya untuk memastikan bahwa struktur mengambil seluruh garis cache untuk mencegah kesalahan berbagi dalam konteks multithreaded. Jika Anda akan memiliki sejumlah besar objek yang akan dikemas secara default, itu dapat menghemat memori dan meningkatkan kinerja cache untuk mengemasnya lebih ketat, meskipun akses memori yang tidak selaras biasanya akan memperlambat segalanya sehingga mungkin ada sisi negatifnya.


0

Perhatikan bahwa ada cara lain untuk mencapai konsistensi data yang ditawarkan oleh paket #pragma (misalnya beberapa orang menggunakan paket #pragma (1) untuk struktur yang harus dikirim melalui jaringan). Sebagai contoh, lihat kode berikut dan output selanjutnya:

#include <stdio.h>

struct a {
    char one;
    char two[2];
    char eight[8];
    char four[4];
};

struct b { 
    char one;
    short two;
    long int eight;
    int four;
};

int main(int argc, char** argv) {
    struct a twoa[2] = {}; 
    struct b twob[2] = {}; 
    printf("sizeof(struct a): %i, sizeof(struct b): %i\n", sizeof(struct a), sizeof(struct b));
    printf("sizeof(twoa): %i, sizeof(twob): %i\n", sizeof(twoa), sizeof(twob));
}

Outputnya adalah sebagai berikut: sizeof (struct a): 15, sizeof (struct b): 24 sizeof (twoa): 30, sizeof (twob): 48

Perhatikan bagaimana ukuran struct a persis apa jumlah byte, tetapi struct b telah menambahkan padding (lihat ini untuk detail tentang padding). Dengan melakukan ini sebagai lawan dari paket #pragma, Anda dapat memiliki kendali untuk mengubah "format kawat" menjadi tipe yang sesuai. Misalnya, "char two [2]" menjadi "int pendek" dan sebagainya.


Tidak itu salah. Jika Anda melihat posisi dalam memori b.two, itu bukan satu byte setelah b.one (kompiler dapat (dan akan sering) menyelaraskan b.two sehingga sejajar dengan akses kata). Untuk a.two, tepat satu byte setelah a.one. Jika Anda perlu mengakses a.two sebagai int pendek, Anda harus memiliki 2 alternatif, baik menggunakan gabungan (tetapi ini biasanya gagal jika Anda memiliki masalah endianness), atau membongkar / mengkonversi dengan kode (menggunakan fungsi ntohX yang sesuai)
xryl669

1
sizeofmengembalikan size_tyang harus dicetak menggunakan%zu . Menggunakan format specifier yang salah memunculkan perilaku yang tidak terdefinisi
phuclv

0

Mengapa seseorang ingin menggunakannya?

Untuk mengurangi memori struktur

Mengapa orang tidak menggunakannya?

  1. Ini dapat menyebabkan penalti kinerja, karena beberapa sistem bekerja lebih baik pada data yang disejajarkan
  2. Beberapa mesin akan gagal membaca data yang tidak selaras
  3. Kode tidak portabel
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.