Mengapa int panjang memakan 12 byte pada beberapa mesin?

26

Saya perhatikan ada yang aneh setelah kompilasi kode ini di komputer saya:

#include <stdio.h>

int main()
{
    printf("Hello, World!\n");

    int a,b,c,d;

    int e,f,g;

    long int h;

    printf("The addresses are:\n %0x \n %0x \n %0x \n %0x \n %0x \n %0x \n %0x \n %0x",
        &a,&b,&c,&d,&e,&f,&g,&h);

    return 0;
}

Hasilnya adalah sebagai berikut. Perhatikan bahwa antara setiap alamat int ada perbedaan 4-byte. Namun antara int terakhir dan int panjang ada perbedaan 12 byte:

 Hello, World!
 The addresses are:

 da54dcac 
 da54dca8 
 da54dca4 
 da54dca0 
 da54dc9c 
 da54dc98 
 da54dc94 
 da54dc88
yoyo_fun
sumber
3
Masukkan yang lain intsetelah hdalam kode sumber. Compiler mungkin meletakkannya di celah, sebelumnya h.
ctrl-alt-delor
32
Jangan gunakan perbedaan antara alamat memori untuk menentukan ukuran. Ada sizeoffungsi untuk itu. printf("size: %d ", sizeof(long));
Chris Schneider
10
Anda hanya mencetak 4 byte alamat Anda yang rendah %x. Beruntung bagi Anda, itu terjadi untuk bekerja dengan benar pada platform Anda untuk melewatkan pointer pointer dengan format string yang diharapkan unsigned int, tetapi pointer dan int adalah ukuran yang berbeda di banyak ABI. Gunakan %puntuk mencetak petunjuk dalam kode portabel. (Sangat mudah untuk membayangkan suatu sistem di mana kode Anda akan mencetak bagian atas / bawah dari 4 petunjuk pertama, bukan setengah dari semua 8.)
Peter Cordes
5
@ChrisSchneider untuk mencetak size_t digunakan%zu . @yoyo_fun untuk mencetak alamat yang digunakan%p . Menggunakan format specifier yang salah memunculkan perilaku yang tidak terdefinisi
phuclv
2
@luu jangan menyebarkan informasi yang salah. Tidak ada kompiler yang layak peduli tentang urutan variabel dinyatakan dalam C. Jika itu peduli, tidak ada alasan mengapa itu akan melakukannya dengan cara yang Anda jelaskan.
gnasher729

Jawaban:

81

Tidak butuh 12 byte, hanya butuh 8. Namun, penyelarasan default untuk int panjang 8 byte pada platform ini adalah 8 byte. Dengan demikian, kompiler perlu memindahkan int panjang ke alamat yang dapat dibagi oleh 8. Alamat "jelas", da54dc8c, tidak dapat dibagi 8 oleh karena itu kesenjangan 12 byte.

Anda harus dapat menguji ini. Jika Anda menambahkan int lain sebelum panjang, jadi ada 8 int, Anda harus menemukan bahwa int panjang akan disejajarkan ok tanpa bergerak. Sekarang hanya 8 byte dari alamat sebelumnya.

Mungkin perlu untuk menunjukkan bahwa, meskipun tes ini harus berhasil, Anda tidak harus bergantung pada variabel yang diatur dengan cara ini. Kompiler AC diizinkan untuk melakukan segala macam hal yang funky untuk membuat program Anda berjalan cepat termasuk memesan ulang variabel (dengan beberapa peringatan).

Alex
sumber
3
perbedaan, bukan kesenjangan.
Deduplicator
10
+ msgstr "termasuk variabel pemesanan ulang". Jika kompiler memutuskan bahwa Anda tidak menggunakan dua variabel pada saat yang sama, itu bebas untuk sebagian tumpang tindih atau sepenuhnya overlay juga ...
Roger Lipscombe
8
Atau memang, simpan di register bukan di tumpukan.
Stop Harming Monica
11
@OrangeDog Saya tidak berpikir itu akan terjadi jika alamat diambil seperti dalam kasus ini tetapi, secara umum, Anda tentu saja benar.
Alex
5
@Alex: Anda bisa mendapatkan hal-hal lucu dengan memori dan register saat mengambil alamat. Mengambil alamat berarti harus memberikannya lokasi memori, tetapi tidak berarti harus benar-benar menggunakannya. Jika Anda mengambil alamat, menetapkan 3 untuk itu dan meneruskannya ke fungsi lain, itu mungkin hanya menulis 3 ke RDI dan memanggil, tidak pernah menuliskannya ke memori. Terkadang mengejutkan dalam debugger.
Zan Lynx
9

Ini karena kompiler Anda menghasilkan padding tambahan antar variabel untuk memastikan mereka tersejajarkan dengan benar dalam memori.

Pada kebanyakan prosesor modern, jika suatu nilai memiliki alamat yang merupakan kelipatan dari ukurannya, itu lebih efisien untuk mengaksesnya. Jika itu diletakkan hdi tempat pertama yang tersedia, alamatnya akan 0xda54dc8c, yang bukan kelipatan 8, jadi akan kurang efisien untuk digunakan. Kompiler tahu tentang ini dan menambahkan sedikit ruang yang tidak digunakan antara dua variabel terakhir Anda untuk memastikan hal itu terjadi.

Jules
sumber
Terima kasih untuk penjelasannya. Bisakah Anda mengarahkan saya ke beberapa bahan mengenai alasan yang mengakses variabel yang lebih dari ukuran mereka lebih efisien? saya ingin tahu mengapa ini terjadi?
yoyo_fun
4
@yoyo_fun dan jika Anda benar - benar ingin memahami memori, maka ada makalah terkenal tentang subjek futuretech.blinkenlights.nl/misc/cpumemory.pdf
Alex
1
@yoyo_fun Cukup sederhana. Beberapa pengontrol memori hanya dapat mengakses kelipatan dari lebar bit prosesor (mis. Prosesor 32-bit hanya dapat secara langsung meminta alamat 0-3, 4-7, 8-11, dll.). Jika Anda meminta alamat yang tidak selaras, prosesor harus membuat dua permintaan memori kemudian memasukkan data ke dalam register. Jadi, kembali ke 32-bit, jika Anda menginginkan nilai yang disimpan di alamat 1, prosesor harus meminta alamat 0-3, 4-7, lalu dapatkan byte dari 1, 2, 3, dan 4. Empat byte dari memori terbaca sia-sia.
phyrfox
2
Titik kecil, tetapi akses memori yang tidak selaras dapat menjadi kesalahan yang tidak dapat diperbaiki alih-alih hit kinerja. Ketergantungan arsitektur.
Jon Chesterfield
1
@ JonChesterfield - Ya. Itu sebabnya saya berkomentar bahwa deskripsi yang saya berikan berlaku untuk sebagian besar arsitektur modern (yang saya maksud sebagian besar adalah x86 dan ARM). Ada orang lain yang berperilaku dengan cara yang berbeda, tetapi mereka pada dasarnya kurang umum. (Menariknya: ARM dulunya adalah salah satu arsitektur yang memerlukan akses yang selaras, tetapi mereka menambahkan penanganan otomatis dari akses yang tidak selaras dalam revisi selanjutnya)
Jules
2

Tes Anda belum tentu menguji apa yang Anda pikirkan, karena tidak ada persyaratan bahasa untuk menghubungkan alamat salah satu variabel lokal ini satu sama lain.

Anda harus meletakkan ini sebagai bidang dalam struct agar dapat menyimpulkan sesuatu tentang alokasi penyimpanan.

Variabel lokal tidak diperlukan untuk berbagi penyimpanan di samping satu sama lain dengan cara tertentu. Kompiler dapat memasukkan variabel sementara di mana saja di dalam tumpukan, misalnya, yang bisa berada di antara dua variabel lokal ini.

Sebaliknya, itu tidak akan diizinkan untuk memasukkan variabel sementara ke dalam sebuah struct, jadi jika Anda mencetak alamat-alamat dari bidang-bidang struct sebagai gantinya, Anda akan membandingkan item-item yang dimaksudkan dialokasikan dari memori logis yang sama dengan chuck (struct).

Erik Eidt
sumber