Mengapa tipe selalu ukuran tertentu tidak peduli nilainya?


149

Implementasi mungkin berbeda antara ukuran sebenarnya dari tipe, tetapi pada kebanyakan, tipe seperti unsigned int dan float selalu 4 byte. Tetapi mengapa suatu tipe selalu menempati jumlah memori tertentu berapa pun nilainya? Misalnya, jika saya membuat integer berikut dengan nilai 255

int myInt = 255;

Kemudian myIntakan menempati 4 byte dengan kompiler saya. Namun, nilai sebenarnya, 255dapat direpresentasikan dengan hanya 1 byte, jadi mengapa myInttidak hanya menempati 1 byte memori? Atau cara bertanya yang lebih umum: Mengapa suatu jenis hanya memiliki satu ukuran yang dikaitkan dengannya ketika ruang yang dibutuhkan untuk mewakili nilai mungkin lebih kecil dari ukuran itu?


15
1) " Namun, nilai aktual, 256 dapat diwakili dengan hanya 1 byte " Salah, nilai terbesar unsinged, yang dapat diwakili dengan 1 byte adalah 255. 2) Pertimbangkan overhead menghitung ukuran penyimpanan optimal, dan menyusut / memperluas area penyimpanan, dari suatu variabel, karena nilainya berubah.
Algirdas Preidžius

99
Nah, ketika tiba saatnya untuk membaca nilai dari memori, bagaimana Anda mengusulkan mesin akan menentukan berapa banyak byte untuk dibaca? Bagaimana mesin akan tahu di mana harus berhenti membaca nilainya? Ini akan membutuhkan fasilitas tambahan. Dan secara umum memori dan overhead kinerja untuk fasilitas tambahan ini akan jauh lebih tinggi daripada jika hanya menggunakan tetap 4 byte untuk unsigned intnilai.
AnT

74
Saya sangat suka pertanyaan ini. Meskipun mungkin tampak sederhana untuk menjawabnya, saya pikir bahwa memberikan penjelasan yang tepat membutuhkan pemahaman yang baik tentang bagaimana komputer dan arsitektur komputer benar-benar bekerja. Kebanyakan orang mungkin akan menerima begitu saja, tanpa memiliki penjelasan yang komprehensif untuk itu.
andreee

37
Pertimbangkan apa yang akan terjadi jika Anda menambahkan 1 ke nilai variabel, menjadikannya 256, sehingga perlu diperluas. Di mana ia berkembang? Apakah Anda memindahkan sisa memori untuk membuat ruang? Apakah variabel itu sendiri bergerak? Jika ya, ke mana perginya dan bagaimana Anda menemukan petunjuk yang perlu Anda perbarui?
molbdnilo

13
@someidiot nggak, Anda salah. std::vector<X>selalu memiliki ukuran yang sama, yaitu sizeof(std::vector<X>)konstanta waktu kompilasi.
SergeyA

Jawaban:


131

Compiler seharusnya menghasilkan assembler (dan akhirnya kode mesin) untuk beberapa mesin, dan umumnya C ++ mencoba bersimpati pada mesin itu.

Bersimpati pada mesin yang mendasarinya secara kasar: membuatnya mudah untuk menulis kode C ++ yang akan memetakan secara efisien ke operasi yang dapat dijalankan mesin dengan cepat. Jadi, kami ingin memberikan akses ke tipe data dan operasi yang cepat dan "alami" pada platform perangkat keras kami.

Konkretnya, pertimbangkan arsitektur mesin tertentu. Mari kita ambil keluarga Intel x86 saat ini.

Manual Pengembang Perangkat Lunak Arsitektur Intel® 64 dan IA-32 vol 1 ( tautan ), bagian 3.4.1 mengatakan:

32-bit register tujuan umum EAX, EBX, ECX, EDX, ESI, EDI, EBP, dan ESP disediakan untuk memegang item berikut:

• Operand untuk operasi logis dan aritmatika

• Operan untuk perhitungan alamat

• Memory pointer

Jadi, kami ingin kompiler menggunakan register EAX, EBX, dll ini saat kompilasi aritmatika C ++ integer sederhana. Ini berarti bahwa ketika saya mendeklarasikan suatu int, itu harus sesuatu yang kompatibel dengan register ini, sehingga saya dapat menggunakannya secara efisien.

Register selalu berukuran sama (di sini, 32 bit), jadi intvariabel saya akan selalu 32 bit juga. Saya akan menggunakan tata letak yang sama (little-endian) sehingga saya tidak perlu melakukan konversi setiap kali saya memuat nilai variabel ke dalam register, atau menyimpan register kembali ke dalam variabel.

Menggunakan godbolt kita bisa melihat persis apa yang dilakukan kompiler untuk beberapa kode sepele:

int square(int num) {
    return num * num;
}

kompilasi (dengan GCC 8.1 dan -fomit-frame-pointer -O3untuk kesederhanaan) ke:

square(int):
  imul edi, edi
  mov eax, edi
  ret

ini berarti:

  1. yang int numparameter disahkan dalam register EDI, berarti itu persis ukuran dan tata letak Intel berharap untuk mendaftar asli. Fungsi tidak harus mengubah apa pun
  2. perkalian adalah instruksi tunggal ( imul), yang sangat cepat
  3. mengembalikan hasilnya hanyalah masalah menyalinnya ke register lain (penelepon mengharapkan hasilnya dimasukkan ke dalam EAX)

Sunting: kita dapat menambahkan perbandingan yang relevan untuk menunjukkan perbedaan menggunakan merek layout non-asli. Kasus paling sederhana adalah menyimpan nilai dalam sesuatu selain lebar asli.

Menggunakan godbolt lagi, kita dapat membandingkan perkalian asli yang sederhana

unsigned mult (unsigned x, unsigned y)
{
    return x*y;
}

mult(unsigned int, unsigned int):
  mov eax, edi
  imul eax, esi
  ret

dengan kode yang setara untuk lebar non-standar

struct pair {
    unsigned x : 31;
    unsigned y : 31;
};

unsigned mult (pair p)
{
    return p.x*p.y;
}

mult(pair):
  mov eax, edi
  shr rdi, 32
  and eax, 2147483647
  and edi, 2147483647
  imul eax, edi
  ret

Semua instruksi tambahan berkaitan dengan mengubah format input (dua bilangan bulat 31-bit yang tidak ditandatangani) ke dalam format yang dapat ditangani prosesor secara asli. Jika kami ingin menyimpan hasilnya kembali ke nilai 31-bit, akan ada satu atau dua instruksi lain untuk melakukan ini.

Kompleksitas ekstra ini berarti Anda hanya akan repot dengan ini ketika penghematan ruang sangat penting. Dalam hal ini kami hanya menyimpan dua bit dibandingkan dengan menggunakan asli unsignedatau uint32_ttipe, yang akan menghasilkan kode yang lebih sederhana.


Catatan tentang ukuran dinamis:

Contoh di atas masih merupakan nilai lebar tetap daripada lebar variabel, tetapi lebar (dan pelurusan) tidak lagi cocok dengan register asli.

Platform x86 memiliki beberapa ukuran asli, termasuk 8-bit dan 16-bit sebagai tambahan untuk 32-bit utama (Saya menggunakan mode 64-bit dan berbagai hal lain untuk kesederhanaan).

Jenis-jenis ini (char, int8_t, uint8_t, int16_t dll) juga secara langsung didukung oleh arsitektur - sebagian untuk kompatibilitas mundur dengan 8086/286/386 / etc yang lebih tua. set instruksi dll.

Sudah pasti bahwa memilih jenis ukuran tetap alami terkecil yang akan cukup, bisa menjadi praktik yang baik - mereka masih cepat, instruksi tunggal memuat dan menyimpan, Anda masih mendapatkan aritmatika asli berkecepatan penuh, dan Anda bahkan dapat meningkatkan kinerja dengan mengurangi kesalahan cache.

Ini sangat berbeda dengan pengodean panjang variabel - Saya telah bekerja dengan beberapa di antaranya, dan mereka mengerikan. Setiap beban menjadi satu lingkaran, bukan satu instruksi. Setiap toko juga merupakan lingkaran. Setiap struktur panjang variabel, jadi Anda tidak bisa menggunakan array secara alami.


Catatan lebih lanjut tentang efisiensi

Dalam komentar selanjutnya, Anda telah menggunakan kata "efisien", sejauh yang saya tahu tentang ukuran penyimpanan. Kami terkadang memilih untuk meminimalkan ukuran penyimpanan - ini bisa menjadi penting ketika kami menyimpan nilai yang sangat besar ke file, atau mengirimkannya melalui jaringan. Imbalannya adalah kita perlu memuat nilai-nilai itu ke dalam register untuk melakukan apa saja dengan mereka, dan melakukan konversi tidak gratis.

Ketika kita membahas efisiensi, kita perlu tahu apa yang kita optimalkan, dan apa timbal baliknya. Menggunakan tipe penyimpanan non-asli adalah salah satu cara untuk memperdagangkan kecepatan pemrosesan untuk ruang, dan terkadang masuk akal. Dengan menggunakan penyimpanan panjang variabel (setidaknya untuk tipe aritmatika), memperdagangkan kecepatan pemrosesan lebih banyak (dan kompleksitas kode dan waktu pengembang) untuk menghemat ruang lebih lanjut yang sering minimal.

Penalti kecepatan yang Anda bayar untuk ini berarti itu hanya bermanfaat ketika Anda harus benar-benar meminimalkan bandwidth atau penyimpanan jangka panjang, dan untuk kasus-kasus itu biasanya lebih mudah untuk menggunakan format yang sederhana dan alami - dan kemudian cukup kompres dengan sistem tujuan umum (seperti zip, gzip, bzip2, xy atau apa pun).


tl; dr

Setiap platform memiliki satu arsitektur, tetapi Anda dapat menghasilkan sejumlah cara berbeda untuk merepresentasikan data. Tidaklah masuk akal jika bahasa apa pun menyediakan jumlah tipe data bawaan yang tidak terbatas. Jadi, C ++ menyediakan akses implisit platform asli, kumpulan tipe data, dan memungkinkan Anda untuk mengkodekan representasi lain (non-pribumi) sendiri.


Saya melihat semua jawaban yang bagus sambil mencoba memahami semuanya .. Jadi, sehubungan dengan jawaban Anda, bukankah ukurannya dinamis, katakanlah kurang dari 32 bit untuk bilangan bulat, tidak hanya mengizinkan lebih banyak variabel dalam register ? Jika endiannya sama, mengapa ini tidak optimal?
Nichlas Uden

7
@ asd tetapi berapa banyak register yang akan Anda gunakan dalam kode yang mengetahui berapa banyak variabel yang saat ini disimpan dalam register?
user253751

1
FWIW itu biasa untuk mengemas beberapa nilai ke dalam ruang terkecil yang tersedia di mana Anda memutuskan penghematan ruang lebih penting daripada biaya kecepatan pengemasan dan membongkar mereka. Anda umumnya tidak dapat beroperasi secara alami dalam bentuk penuh karena prosesor tidak tahu bagaimana melakukan perhitungan dengan benar pada apa pun selain dari register bawaannya. Cari BCD untuk pengecualian parsial dengan dukungan prosesor
Berguna

3
Jika saya benar - benar membutuhkan semua 32 bit untuk beberapa nilai, saya masih membutuhkan tempat untuk menyimpan panjangnya, jadi sekarang saya membutuhkan lebih dari 32 bit dalam beberapa kasus.
berguna

1
+1. Catatan tentang "format sederhana dan alami dan kemudian kompres" biasanya lebih baik: Ini jelas benar secara umum , tetapi : untuk beberapa data VLQ-masing-masing-nilai-lalu-kompres-semuanya-melakukan jauh lebih baik daripada hanya mengompres-itu -seluruhnya, dan untuk beberapa aplikasi, data Anda tidak dapat dikompres bersama-sama , karena itu terpisah (seperti dalam gitmetadata) atau Anda benar-benar menyimpannya dalam memori yang kadang-kadang perlu secara acak mengakses atau memodifikasi beberapa tetapi tidak sebagian besar dari nilai-nilai (seperti dalam mesin rendering HTML + CSS), dan dengan demikian hanya dapat dicegah menggunakan sesuatu seperti VLQ di tempat.
mtraceur

139

Karena tipe secara mendasar mewakili penyimpanan, dan mereka didefinisikan dalam hal nilai maksimum yang dapat mereka pegang, bukan nilai saat ini.

Analogi yang sangat sederhana adalah rumah - rumah memiliki ukuran tetap, berapapun jumlah orang yang tinggal di dalamnya, dan ada juga kode bangunan yang menetapkan jumlah maksimum orang yang dapat tinggal di rumah dengan ukuran tertentu.

Namun, bahkan jika satu orang tinggal di sebuah rumah yang dapat menampung 10 orang, ukuran rumah tersebut tidak akan terpengaruh oleh jumlah penghuni saat ini.


31
Saya suka analoginya. Jika kita sedikit memperluasnya, kita bisa membayangkan menggunakan bahasa pemrograman yang tidak menggunakan ukuran memori tetap untuk tipe, dan itu akan mirip dengan merobohkan kamar di rumah kita setiap kali mereka tidak digunakan, dan membangunnya kembali ketika diperlukan (Yaitu ton overhead ketika kita hanya bisa membangun banyak rumah dan membiarkannya ketika kita membutuhkan).
ahouse101

5
"Karena jenis pada dasarnya mewakili penyimpanan" ini tidak benar untuk semua bahasa (seperti naskah, misalnya)
corvus_192

56
@corvus_192 tag memiliki arti. Pertanyaan ini ditandai dengan C ++, bukan 'naskah'
SergeyA

4
@ ahouse101 Memang, ada sejumlah bahasa yang memiliki integer presisi tak terbatas, mereka tumbuh sesuai kebutuhan. Bahasa-bahasa ini tidak mengharuskan Anda untuk mengalokasikan memori tetap untuk variabel, mereka diterapkan secara internal sebagai referensi objek. Contoh: Lisp, Python.
Barmar

2
@ jamesqf Mungkin bukan kebetulan bahwa aritmatika MP pertama kali dianut di Lisp, yang juga melakukan manajemen memori otomatis. Para desainer merasa bahwa dampak kinerja adalah sekunder dari kemudahan pemrograman. Dan teknik optimasi dikembangkan untuk meminimalkan dampak.
Barmar

44

Ini adalah optimasi dan penyederhanaan.

Anda dapat memiliki objek berukuran tetap. Dengan demikian menyimpan nilai.
Atau Anda dapat memiliki objek berukuran variabel. Tetapi menyimpan nilai dan ukuran.

benda berukuran tetap

Kode yang memanipulasi angka tidak perlu khawatir tentang ukuran. Anda berasumsi bahwa Anda selalu menggunakan 4 byte dan membuat kodenya sangat sederhana.

Benda berukuran dinamis

Kode yang harus dipahami oleh angka yang dimanipulasi ketika membaca variabel bahwa itu harus membaca nilai dan ukuran. Gunakan ukuran untuk memastikan semua bit tinggi nol di register.

Ketika menempatkan kembali nilai dalam memori jika nilai belum melebihi ukuran saat ini maka cukup tempatkan kembali nilai dalam memori. Tetapi jika nilainya menyusut atau bertambah, Anda perlu memindahkan lokasi penyimpanan objek ke lokasi lain dalam memori untuk memastikan tidak meluap. Sekarang Anda harus melacak posisi nomor itu (karena dapat bergerak jika tumbuh terlalu besar untuk ukurannya). Anda juga perlu melacak semua lokasi variabel yang tidak digunakan sehingga berpotensi digunakan kembali.

Ringkasan

Kode yang dihasilkan untuk objek ukuran tetap jauh lebih sederhana.

Catatan

Kompresi menggunakan fakta bahwa 255 akan masuk ke dalam satu byte. Ada skema kompresi untuk menyimpan set data besar yang akan secara aktif menggunakan nilai ukuran yang berbeda untuk angka yang berbeda. Tetapi karena ini bukan data langsung, Anda tidak memiliki kompleksitas yang dijelaskan di atas. Anda menggunakan lebih sedikit ruang untuk menyimpan data dengan biaya mengompresi / mengurangi kompresi data untuk penyimpanan.


4
Ini adalah jawaban terbaik bagi saya: Bagaimana Anda melacak ukurannya? Dengan lebih banyak memori?
online Thomas

@ThomasMoors Ya, persis: dengan lebih banyak memori. Jika Anda, misalnya memiliki array dinamis, maka beberapa intakan menyimpan jumlah elemen dalam array itu. Itu intsendiri akan memiliki ukuran yang tetap lagi.
Alfe

1
@ThomasMoors ada dua opsi yang biasa digunakan, keduanya membutuhkan memori ekstra - baik Anda memiliki bidang (ukuran tetap) yang memberitahu Anda berapa banyak data yang ada (misalnya int untuk ukuran array, atau string "gaya pascal" di mana yang pertama elemen berisi berapa banyak karakter yang ada), atau sebagai alternatif Anda dapat memiliki rantai (atau struktur yang lebih kompleks) di mana setiap elemen entah bagaimana mencatatnya jika itu adalah yang terakhir - mis. string tanpa putus, atau sebagian besar bentuk daftar yang ditautkan.
Peteris

27

Karena dalam bahasa seperti C ++, tujuan desain adalah operasi sederhana dikompilasi ke instruksi mesin sederhana.

Semua set instruksi CPU arus utama bekerja dengan tipe lebar tetap , dan jika Anda ingin melakukan tipe lebar variabel , Anda harus melakukan beberapa instruksi mesin untuk menanganinya.

Adapun mengapa perangkat keras komputer yang mendasarinya seperti itu: Itu karena lebih sederhana, dan lebih efisien untuk banyak kasus (tetapi tidak semua).

Bayangkan komputer sebagai selembar kaset:

| xx | xx | xx | xx | xx | xx | xx | xx | xx | xx | xx | xx | xx | ...

Jika Anda hanya memberi tahu komputer untuk melihat byte pertama pada kaset itu xx, bagaimana ia tahu apakah jenisnya berhenti di sana, atau berlanjut ke byte berikutnya? Jika Anda memiliki angka seperti 255(heksadesimal FF) atau angka seperti 65535(heksadesimal FFFF) byte pertama selalu FF.

Jadi, bagaimana Anda tahu? Anda harus menambahkan logika tambahan, dan "membebani" arti setidaknya satu bit atau nilai byte untuk menunjukkan bahwa nilai berlanjut ke byte berikutnya. Logika itu tidak pernah "gratis", baik Anda meniru dalam perangkat lunak atau Anda menambahkan banyak transistor tambahan ke CPU untuk melakukannya.

Jenis bahasa dengan lebar tetap seperti C dan C ++ mencerminkan hal itu.

Tidak harus seperti ini, dan lebih banyak bahasa abstrak yang kurang mementingkan pemetaan ke kode efisien maksimum bebas menggunakan pengkodean lebar variabel (juga dikenal sebagai "Jumlah Panjang Variabel" atau VLQ) untuk jenis numerik.

Bacaan lebih lanjut: Jika Anda mencari "jumlah panjang variabel" Anda dapat menemukan beberapa contoh di mana pengkodean semacam itu sebenarnya efisien dan layak logika tambahan. Biasanya ketika Anda perlu menyimpan sejumlah besar nilai yang mungkin ada di mana saja dalam rentang besar, tetapi sebagian besar nilai cenderung mengarah ke beberapa sub-rentang kecil.


Perhatikan bahwa jika kompilator dapat membuktikan bahwa ia dapat menyimpan nilai dalam jumlah yang lebih kecil tanpa melanggar kode apa pun (misalnya, itu hanya variabel yang terlihat secara internal di dalam satu unit terjemahan), dan heuristik optimasinya menunjukkan bahwa ia ' Akan lebih efisien pada perangkat keras target, itu sepenuhnya diperbolehkan untuk mengoptimalkannya dan menyimpannya dalam jumlah yang lebih kecil, selama kode lainnya bekerja "seolah-olah" itu melakukan hal standar.

Tetapi , ketika kode harus dioperasikan dengan kode lain yang mungkin dikompilasi secara terpisah, ukuran harus tetap konsisten, atau memastikan bahwa setiap bagian dari kode mengikuti konvensi yang sama.

Karena jika tidak konsisten, ada komplikasi ini: Bagaimana jika saya miliki int x = 255;tetapi kemudian dalam kode saya lakukan x = y? Jika intbisa lebar variabel, kompiler harus tahu sebelumnya untuk mengalokasikan jumlah ruang maksimum yang dibutuhkan. Itu tidak selalu mungkin, karena bagaimana jika yargumen yang diteruskan dari sepotong kode lain yang dikompilasi secara terpisah?


26

Java menggunakan kelas yang disebut "BigInteger" dan "BigDecimal" untuk melakukan hal ini, seperti halnya antarmuka kelas GMP C ++ C ++ (terima kasih Digital Trauma). Anda dapat dengan mudah melakukannya sendiri dalam hampir semua bahasa apa pun jika Anda mau.

CPU selalu memiliki kemampuan untuk menggunakan BCD (Binary Coded Decimal) yang dirancang untuk mendukung operasi dengan panjang berapa pun (tetapi Anda cenderung beroperasi secara manual pada satu byte pada suatu waktu yang akan lambat oleh standar GPU saat ini.)

Alasan kami tidak menggunakan ini atau solusi serupa lainnya? Performa. Bahasa Anda yang paling berkinerja tinggi tidak mampu memperluas variabel di tengah beberapa operasi loop ketat - itu akan sangat non-deterministik.

Dalam penyimpanan massal dan situasi transportasi, nilai yang dikemas seringkali merupakan jenis nilai HANYA yang akan Anda gunakan. Misalnya, paket musik / video yang dialirkan ke komputer Anda mungkin menghabiskan sedikit untuk menentukan apakah nilai berikutnya adalah 2 byte atau 4 byte sebagai ukuran optimasi.

Setelah itu di komputer Anda di mana ia dapat digunakan, memori murah tetapi kecepatan dan kerumitan variabel resizable tidak .. itu benar-benar satu-satunya alasan.


4
Senang melihat seseorang menyebutkan BigInteger. Bukannya itu ide yang konyol, hanya saja masuk akal untuk melakukannya untuk jumlah yang sangat besar.
Max Barraclough

1
Menjadi bertele-tele sebenarnya angka yang sangat tepat :) Yah setidaknya dalam kasus BigDecimal ...
Bill K

2
Dan karena ini ditandai c ++ , mungkin layak menyebutkan antarmuka kelas GMP C ++ , yang merupakan ide yang sama dengan Java's Big *.
Digital Trauma

20

Karena itu akan sangat rumit dan komputasi berat untuk memiliki tipe sederhana dengan ukuran dinamis. Saya tidak yakin apakah ini akan mungkin terjadi.
Komputer harus memeriksa berapa banyak bit nomor yang diambil setelah setiap perubahan nilainya. Ini akan menjadi operasi tambahan yang cukup banyak. Dan akan jauh lebih sulit untuk melakukan perhitungan ketika Anda tidak tahu ukuran variabel selama kompilasi.

Untuk mendukung ukuran variabel yang dinamis, komputer sebenarnya harus mengingat berapa banyak byte yang dimiliki variabel saat ini ... yang membutuhkan memori tambahan untuk menyimpan informasi itu. Dan informasi ini harus dianalisis sebelum setiap operasi pada variabel untuk memilih instruksi prosesor yang tepat.

Untuk lebih memahami cara kerja komputer dan mengapa variabel memiliki ukuran konstan, pelajari dasar-dasar bahasa assembler.

Meskipun, saya kira akan mungkin untuk mencapai sesuatu seperti itu dengan nilai-nilai constexpr. Namun, ini akan membuat kode kurang dapat diprediksi oleh seorang programmer. Saya kira beberapa optimisasi kompiler dapat melakukan sesuatu seperti itu tetapi mereka menyembunyikannya dari seorang programmer untuk menjaga hal-hal sederhana.

Saya jelaskan di sini hanya masalah yang menyangkut kinerja suatu program. Saya menghilangkan semua masalah yang harus diselesaikan untuk menghemat memori dengan mengurangi ukuran variabel. Sejujurnya, saya pikir itu tidak mungkin.


Kesimpulannya, menggunakan variabel yang lebih kecil dari yang dideklarasikan memiliki arti hanya jika nilai-nilainya diketahui selama kompilasi. Sangat mungkin bahwa kompiler modern melakukan itu. Dalam kasus lain, itu akan menyebabkan terlalu banyak masalah sulit atau bahkan tidak terpecahkan.


Saya sangat ragu bahwa hal seperti itu dilakukan selama waktu kompilasi. Tidak ada gunanya menyimpan memori kompiler seperti itu, dan itulah satu-satunya keuntungan.
Bartek Banachewicz

1
Saya lebih memikirkan operasi seperti mengalikan variabel constexpr dengan variabel normal. Sebagai contoh kita memiliki (secara teoritis) 8-byte variabel constexpr dengan nilai 56dan kita mengalikannya dengan beberapa variabel 2-byte. Pada beberapa arsitektur, operasi 64-bit akan lebih berat perhitungannya sehingga kompiler dapat mengoptimalkannya agar hanya melakukan penggandaan 16-bit.
NO_NAME

Beberapa implementasi APL dan beberapa bahasa dalam keluarga SNOBOL (saya kira SPITBOL? Mungkin Ikon) melakukan hal ini (dengan rincian): mengubah format representasi secara dinamis tergantung pada nilai aktual. APL akan beralih dari Boolean ke integer ke float dan back. SPITBOL akan beralih dari representasi kolom Boolean (8 array Boolean terpisah yang disimpan dalam array byte) ke integer (IIRC).
davidbak

16

Kemudian myIntakan menempati 4 byte dengan kompiler saya. Namun, nilai sebenarnya, 255dapat direpresentasikan dengan hanya 1 byte, jadi mengapa myInttidak hanya menempati 1 byte memori?

Ini dikenal sebagai pengkodean panjang variabel , ada berbagai pengkodean yang didefinisikan, misalnya VLQ . Namun, salah satu yang paling terkenal mungkin adalah UTF-8 : UTF-8 mengkodekan poin kode pada sejumlah variabel byte, dari 1 hingga 4.

Atau cara bertanya yang lebih umum: Mengapa suatu jenis hanya memiliki satu ukuran yang dikaitkan dengannya ketika ruang yang dibutuhkan untuk mewakili nilai mungkin lebih kecil dari ukuran itu?

Seperti biasa dalam rekayasa, ini semua tentang pertukaran. Tidak ada solusi yang hanya memiliki kelebihan, jadi Anda harus menyeimbangkan keuntungan dan pertukaran saat merancang solusi Anda.

Desain yang ditetapkan adalah menggunakan tipe fundamental ukuran tetap, dan perangkat keras / bahasa baru saja terbang turun dari sana.

Jadi, apa kelemahan mendasar dari pengkodean variabel , yang menyebabkannya ditolak demi skema memori yang lebih banyak? Tidak Mengatasi Secara Acak .

Apa indeks byte di mana titik kode ke-4 dimulai pada string UTF-8?

Tergantung pada nilai-nilai poin kode sebelumnya, pemindaian linear diperlukan.

Tentunya ada skema pengkodean panjang variabel yang lebih baik dalam pengalamatan acak?

Ya, tetapi mereka juga lebih rumit. Jika ada yang ideal, saya belum pernah melihatnya.

Apakah Random Addressing benar-benar penting?

Oh ya!

Masalahnya, segala jenis agregat / array bergantung pada tipe ukuran tetap:

  • Mengakses bidang ke-3 a struct? Mengatasi secara Acak!
  • Mengakses elemen ke-3 dari array? Mengatasi secara Acak!

Yang berarti Anda pada dasarnya memiliki trade-off berikut:

Jenis ukuran tetap ATAU Pemindaian memori linier


Ini bukan masalah sebanyak Anda membuatnya terdengar. Anda selalu dapat menggunakan tabel vektor. Ada overhead memori dan pengambilan ekstra tetapi pemindaian linear tidak diperlukan.
Artelius

2
@ Artelius: Bagaimana Anda menyandikan tabel vektor ketika bilangan bulat memiliki lebar variabel? Juga, berapakah overhead memori dari tabel vektor saat menyandikan satu untuk bilangan bulat yang menggunakan 1 hingga 4 byte dalam memori?
Matthieu M.

Lihat, Anda benar, dalam contoh spesifik OP memberi, menggunakan tabel vektor tidak memiliki keunggulan. Alih-alih membangun tabel vektor, Anda mungkin juga meletakkan data dalam array elemen ukuran tetap. Namun, OP juga meminta jawaban yang lebih umum. Dalam Python, array bilangan bulat adalah tabel vektor bilangan bulat berukuran variabel! Itu bukan karena itu memecahkan masalah ini , tetapi karena Python tidak tahu pada waktu kompilasi apakah elemen daftar akan menjadi Integer, Mengapung, Diktik, Strings, atau Daftar, yang semuanya tentu saja memiliki ukuran yang berbeda.
Artelius

@ Artelius: Perhatikan bahwa dalam Python array berisi pointer ukuran tetap ke elemen; ini membuatnya O (1) untuk sampai ke suatu elemen, dengan biaya tipuan.
Matthieu M.

16

Memori komputer dibagi lagi menjadi potongan-potongan yang ditangani secara berurutan dengan ukuran tertentu (seringkali 8 bit, dan disebut sebagai byte), dan sebagian besar komputer dirancang untuk secara efisien mengakses urutan byte yang memiliki alamat berurutan.

Jika alamat objek tidak pernah berubah dalam masa hidup objek, maka kode yang diberikan alamatnya dapat dengan cepat mengakses objek yang dimaksud. Keterbatasan penting dengan pendekatan ini, bagaimanapun, adalah bahwa jika suatu alamat ditugaskan untuk alamat X, dan kemudian alamat lain ditugaskan untuk alamat Y yang berjarak N byte, maka X tidak akan dapat tumbuh lebih besar dari N byte dalam masa pakai. Y, kecuali X atau Y dipindahkan. Agar X dapat bergerak, segala sesuatu di alam semesta yang memegang alamat X perlu diperbarui agar mencerminkan yang baru, dan juga agar Y dapat bergerak. Meskipun dimungkinkan untuk merancang sistem untuk memfasilitasi pembaruan semacam itu (baik Java dan .NET mengelolanya dengan cukup baik), jauh lebih efisien untuk bekerja dengan objek yang akan tetap berada di lokasi yang sama sepanjang masa hidup mereka,


"X tidak akan dapat tumbuh lebih besar dari N byte dalam masa Y, kecuali jika X atau Y dipindahkan. Agar X dapat bergerak, akan perlu bahwa segala sesuatu di alam semesta yang memegang alamat X diperbarui untuk mencerminkan yang baru, dan juga agar Y bergerak. " Ini adalah poin penting IMO: objek yang hanya menggunakan ukuran sebanyak kebutuhan nilai saat ini perlu menambahkan ton overhead untuk ukuran / sentinel, pemindahan memori, grafik referensi, dll. Dan cukup jelas ketika seseorang merenungkan bagaimana itu bisa bekerja ... tapi tetap saja, sangat layak untuk dinyatakan dengan sangat jelas, terutama seperti yang dilakukan beberapa orang lainnya.
underscore_d

@underscore_d: Bahasa seperti Javascript yang dirancang dari bawah ke atas untuk menangani objek berukuran variabel bisa sangat efisien dalam hal itu. Di sisi lain, walaupun dimungkinkan untuk membuat sistem objek berukuran variabel sederhana, dan dimungkinkan untuk membuatnya cepat, implementasi sederhana lambat dan implementasi cepat sangat kompleks.
supercat

13

Jawaban singkatnya adalah: Karena standar C ++ mengatakan demikian.

Jawaban panjangnya adalah: Apa yang dapat Anda lakukan pada komputer pada akhirnya dibatasi oleh perangkat keras. Tentu saja dimungkinkan untuk menyandikan integer ke dalam jumlah variabel byte untuk penyimpanan, tetapi kemudian membacanya akan membutuhkan instruksi CPU khusus untuk menjadi pemain, atau Anda dapat mengimplementasikannya dalam perangkat lunak, tetapi kemudian akan sangat lambat. Operasi ukuran tetap tersedia di CPU untuk memuat nilai lebar yang telah ditentukan, tidak ada untuk lebar variabel.

Poin lain yang perlu dipertimbangkan adalah bagaimana memori komputer bekerja. Katakanlah tipe integer Anda dapat memakan waktu antara 1 hingga 4 byte penyimpanan. Misalkan Anda menyimpan nilai 42 ke dalam bilangan bulat Anda: ini membutuhkan 1 byte, dan Anda menempatkannya di alamat memori X. Kemudian Anda menyimpan variabel berikutnya di lokasi X + 1 (Saya tidak mempertimbangkan penyelarasan pada titik ini) dan seterusnya . Kemudian Anda memutuskan untuk mengubah nilai Anda menjadi 6424.

Tetapi ini tidak cocok dengan satu byte! Jadi apa yang kamu lakukan? Di mana Anda meletakkan sisanya? Anda sudah memiliki sesuatu di X +1, jadi tidak dapat menempatkannya di sana. Di tempat lain? Bagaimana Anda tahu di mana nanti? Memori komputer tidak mendukung semantik sisipkan: Anda tidak bisa begitu saja meletakkan sesuatu di lokasi dan mendorong semuanya setelah itu menyisihkan ruang!

Selain: Apa yang Anda bicarakan sebenarnya adalah bidang kompresi data. Algoritma kompresi ada untuk mengemas semuanya lebih ketat, jadi setidaknya beberapa dari mereka akan mempertimbangkan untuk tidak menggunakan lebih banyak ruang untuk integer Anda daripada yang dibutuhkan. Namun, data terkompresi tidak mudah untuk dimodifikasi (jika mungkin sama sekali) dan akhirnya dikompres ulang setiap kali Anda melakukan perubahan apa pun terhadapnya.


11

Ada manfaat kinerja runtime yang cukup besar dari melakukan ini. Jika Anda beroperasi pada tipe ukuran variabel, Anda harus mendekodekan setiap angka sebelum melakukan operasi (instruksi kode mesin biasanya lebar tetap), lakukan operasi, kemudian temukan ruang di memori yang cukup besar untuk menampung hasilnya. Itu adalah operasi yang sangat sulit. Jauh lebih mudah untuk menyimpan semua data dengan sedikit tidak efisien.

Ini tidak selalu bagaimana hal itu dilakukan. Pertimbangkan protokol Protobuf Google. Protobuf dirancang untuk mengirimkan data dengan sangat efisien. Mengurangi jumlah byte yang dikirimkan sebanding dengan biaya instruksi tambahan saat mengoperasikan data. Karenanya, protobuf menggunakan enkode yang mengkodekan integer dalam 1, 2, 3, 4, atau 5 byte, dan integer yang lebih kecil membutuhkan byte lebih sedikit. Namun, setelah pesan diterima, pesan itu dibongkar ke dalam format integer ukuran tetap yang lebih tradisional yang lebih mudah dioperasikan. Hanya selama transmisi jaringan mereka menggunakan integer panjang variabel yang efisien ruang.


11

Saya suka analogi rumah Sergey , tetapi saya pikir analogi mobil akan lebih baik.

Bayangkan tipe variabel sebagai tipe mobil dan orang sebagai data. Ketika kami sedang mencari mobil baru, kami memilih salah satu yang paling sesuai dengan tujuan kami. Apakah kita menginginkan mobil pintar kecil yang hanya dapat memuat satu atau dua orang? Atau limusin untuk mengangkut lebih banyak orang? Keduanya memiliki kelebihan dan kekurangan seperti kecepatan dan jarak tempuh gas (kecepatan berpikir dan penggunaan memori).

Jika Anda memiliki limusin dan Anda mengemudi sendiri, itu tidak akan menyusut hanya untuk Anda. Untuk melakukan itu, Anda harus menjual mobil (baca: deallocate) dan beli yang lebih kecil untuk Anda sendiri.

Melanjutkan analoginya, Anda dapat menganggap memori sebagai tempat parkir besar yang penuh dengan mobil, dan ketika Anda membaca, seorang sopir khusus yang dilatih khusus untuk jenis mobil Anda akan mengambilkannya untuk Anda. Jika mobil Anda dapat mengubah jenis tergantung pada orang-orang di dalamnya, Anda perlu membawa sejumlah besar sopir setiap kali Anda ingin mendapatkan mobil Anda karena mereka tidak akan pernah tahu jenis mobil apa yang akan duduk di tempat.

Dengan kata lain, mencoba menentukan berapa banyak memori yang perlu Anda baca pada saat dijalankan akan sangat tidak efisien dan lebih besar daripada fakta bahwa Anda mungkin dapat memuat beberapa mobil lagi di tempat parkir Anda.


10

Ada beberapa alasan. Salah satunya adalah kerumitan tambahan untuk menangani angka berukuran sewenang-wenang dan kinerja ini memberikan karena kompiler tidak dapat lagi mengoptimalkan berdasarkan pada asumsi bahwa setiap int persis panjang X byte.

Yang kedua adalah bahwa menyimpan tipe sederhana dengan cara ini berarti mereka membutuhkan byte tambahan untuk menahan panjangnya. Jadi, nilai 255 atau kurang sebenarnya membutuhkan dua byte dalam sistem baru ini, bukan satu, dan dalam kasus terburuk Anda sekarang membutuhkan 5 byte, bukan 4. Ini berarti bahwa kinerja menang dalam hal memori yang digunakan kurang dari yang Anda mungkin berpikir dan dalam beberapa kasus tepi mungkin sebenarnya merupakan kerugian bersih.

Alasan ketiga adalah bahwa memori komputer umumnya dapat dialamatkan dalam kata-kata , bukan byte. (Tapi lihat catatan kaki). Kata-kata adalah kelipatan byte, biasanya 4 pada sistem 32-bit dan 8 pada sistem 64 bit. Anda biasanya tidak dapat membaca byte individual, Anda membaca sebuah kata dan mengekstrak byte ke-n dari kata itu. Ini berarti bahwa mengekstraksi masing-masing byte dari sebuah kata memerlukan sedikit usaha lebih dari sekadar membaca seluruh kata dan bahwa itu sangat efisien jika seluruh memori dibagi secara merata dalam potongan-potongan berukuran kata (yaitu, ukuran 4-byte). Karena, jika Anda memiliki bilangan bulat berukuran sembarang yang mengambang, Anda mungkin berakhir dengan satu bagian dari bilangan bulat berada dalam satu kata, dan yang lain di kata berikutnya, mengharuskan dua bacaan untuk mendapatkan bilangan bulat penuh.

Catatan Kaki: Untuk lebih tepatnya, saat Anda berbicara dalam byte, sebagian besar sistem mengabaikan byte 'tidak rata'. Yaitu, alamat 0, 1, 2 dan 3 semua membaca kata yang sama, 4, 5, 6 dan 7 membaca kata berikutnya, dan seterusnya.

Pada catatan yang tidak dirilis, ini juga mengapa sistem 32-bit memiliki memori maksimal 4 GB. Register yang digunakan untuk alamat lokasi dalam memori biasanya cukup besar untuk menampung kata, yaitu 4 byte, yang memiliki nilai maksimum (2 ^ 32) -1 = 4294967295. 4294967296 byte adalah 4 GB.


8

Ada beberapa objek yang dalam beberapa hal memiliki ukuran variabel, di pustaka standar C ++, seperti std::vector. Namun, ini semua secara dinamis mengalokasikan memori tambahan yang mereka butuhkan. Jika Anda mengambil sizeof(std::vector<int>), Anda akan mendapatkan konstanta yang tidak ada hubungannya dengan memori yang dikelola oleh objek, dan jika Anda mengalokasikan array atau struktur yang mengandung std::vector<int>, itu akan memesan ukuran dasar ini daripada meletakkan penyimpanan tambahan dalam array atau struktur yang sama . Ada beberapa potong sintaks C yang mendukung sesuatu seperti ini, terutama array dan struktur panjang variabel, tetapi C ++ tidak memilih untuk mendukungnya.

Standar bahasa mendefinisikan ukuran objek seperti itu sehingga kompiler dapat menghasilkan kode yang efisien. Sebagai contoh, jika intkebetulan panjangnya 4 byte pada beberapa implementasi, dan Anda mendeklarasikan asebagai pointer ke atau array intnilai, kemudian a[i]menerjemahkannya ke dalam pseudocode, "dereference the address a + 4 × i." Ini dapat dilakukan dalam waktu yang konstan, dan merupakan operasi yang umum dan penting sehingga banyak arsitektur set instruksi, termasuk x86 dan mesin-mesin DEC PDP di mana C awalnya dikembangkan, dapat melakukannya dalam instruksi mesin tunggal.

Satu contoh dunia nyata yang umum dari data yang disimpan secara berurutan sebagai satuan panjang variabel adalah string yang dikodekan sebagai UTF-8. (Namun, tipe yang mendasari string UTF-8 ke compiler masih chardan memiliki lebar 1. Ini memungkinkan string ASCII ditafsirkan sebagai UTF-8 yang valid, dan banyak kode perpustakaan seperti strlen()dan strncpy()untuk terus bekerja.) Pengkodean dari setiap codepoint UTF-8 bisa sepanjang satu hingga empat byte, dan oleh karena itu, jika Anda menginginkan codepoint UTF-8 kelima dalam sebuah string, ia bisa mulai dari byte kelima hingga byte ketujuh data. Satu-satunya cara untuk menemukannya adalah dengan memindai dari awal string dan memeriksa ukuran masing-masing codepoint. Jika Anda ingin menemukan grapheme kelima, Anda juga perlu memeriksa kelas karakter. Jika Anda ingin menemukan karakter UTF-8 juta dalam sebuah string, Anda harus menjalankan loop ini jutaan kali! Jika Anda tahu Anda harus sering bekerja dengan indeks, Anda dapat melintasi string sekali dan membuat indeksnya — atau Anda dapat mengonversi ke pengodean dengan lebar tetap, seperti UCS-4. Menemukan karakter UCS-4 juta dalam sebuah string hanyalah masalah menambahkan empat juta ke alamat array.

Komplikasi lain dengan data panjang variabel adalah bahwa, ketika Anda mengalokasikannya, Anda harus mengalokasikan memori sebanyak mungkin yang dapat digunakan, atau mengalokasikan kembali secara dinamis sesuai kebutuhan. Mengalokasikan untuk kasus terburuk bisa sangat boros. Jika Anda memerlukan blok memori berturut-turut, realokasi dapat memaksa Anda untuk menyalin semua data ke lokasi yang berbeda, tetapi membiarkan memori disimpan dalam potongan yang tidak berurutan akan mempersulit logika program.

Jadi, itu mungkin untuk memiliki bignums variabel-panjang bukan fixed-width short int, int, long intdan long long int, tetapi akan tidak efisien untuk mengalokasikan dan menggunakannya. Selain itu, semua CPU arus utama dirancang untuk melakukan aritmatika pada register dengan lebar tetap, dan tidak ada yang memiliki instruksi yang langsung beroperasi pada beberapa jenis bignum panjang variabel. Itu perlu diimplementasikan dalam perangkat lunak, jauh lebih lambat.

Di dunia nyata, sebagian besar (tetapi tidak semua) programmer telah memutuskan bahwa manfaat dari pengkodean UTF-8, terutama kompatibilitas, adalah penting, dan bahwa kita sangat jarang peduli tentang apa pun selain memindai string dari depan ke belakang atau menyalin blok dari memori bahwa kelemahan lebar variabel dapat diterima. Kita bisa menggunakan elemen lebar variabel yang dikemas mirip dengan UTF-8 untuk hal lain. Tetapi kami sangat jarang melakukannya, dan mereka tidak ada di perpustakaan standar.


7

Mengapa suatu tipe hanya memiliki satu ukuran yang terkait dengannya ketika ruang yang dibutuhkan untuk mewakili nilai mungkin lebih kecil dari ukuran itu?

Terutama karena persyaratan pelurusan.

Sesuai basic.align / 1 :

Jenis objek memiliki persyaratan pelurusan yang menempatkan pembatasan pada alamat di mana objek jenis itu dapat dialokasikan.

Pikirkan sebuah bangunan yang memiliki banyak lantai dan setiap lantai memiliki banyak kamar.
Setiap kamar adalah ukuran Anda (ruang tetap) yang mampu menampung sejumlah orang atau benda.
Dengan ukuran ruangan yang diketahui sebelumnya, itu membuat komponen struktural bangunan terstruktur dengan baik .

Jika kamar tidak sejajar, maka kerangka bangunan tidak akan terstruktur dengan baik.


7

Itu bisa lebih sedikit. Pertimbangkan fungsinya:

int foo()
{
    int bar = 1;
    int baz = 42;
    return bar+baz;
}

itu mengkompilasi ke kode assembly (g ++, x64, detail dilucuti)

$43, %eax
ret

Di sini, bardan bazakhirnya menggunakan nol byte untuk mewakili.


5

jadi mengapa myInt tidak hanya menempati 1 byte memori?

Karena Anda menyuruhnya menggunakan sebanyak itu. Saat menggunakan unsigned int, beberapa standar menentukan bahwa 4 byte akan digunakan dan kisaran yang tersedia untuknya adalah dari 0 hingga 4.294.967.295. Jika Anda menggunakanunsigned char bukan, Anda mungkin hanya akan menggunakan 1 byte yang Anda cari, (tergantung pada standar dan C ++ biasanya menggunakan standar ini).

Jika bukan karena standar-standar ini, Anda harus mengingat ini: bagaimana kompiler atau CPU seharusnya tahu hanya menggunakan 1 byte, bukan 4? Kemudian dalam program Anda, Anda dapat menambah atau mengalikan nilai itu, yang akan membutuhkan lebih banyak ruang. Setiap kali Anda membuat alokasi memori, OS harus menemukan, memetakan, dan memberi Anda ruang itu, (berpotensi menukar memori ke RAM virtual juga); ini bisa memakan waktu lama. Jika Anda mengalokasikan memori sebelumnya, Anda tidak perlu menunggu alokasi lain selesai.

Adapun alasan mengapa kita menggunakan 8 bit per byte, Anda dapat melihat ini: Bagaimana sejarah mengapa byte delapan bit?

Di samping catatan, Anda bisa membiarkan bilangan bulat meluap; tetapi jika Anda menggunakan integer yang ditandatangani, standar C \ C ++ menyatakan bahwa integer overflow menghasilkan perilaku yang tidak ditentukan. Overflow bilangan bulat


5

Sesuatu yang sederhana yang sepertinya terlewatkan oleh sebagian besar jawaban:

karena sesuai dengan tujuan desain C ++.

Mampu menghitung ukuran tipe pada waktu kompilasi memungkinkan sejumlah besar asumsi penyederhanaan dibuat oleh kompiler dan programmer, yang membawa banyak manfaat, terutama yang berkaitan dengan kinerja. Tentu saja, tipe ukuran tetap memiliki jebakan bersamaan seperti integer overflow. Inilah sebabnya mengapa bahasa yang berbeda membuat keputusan desain yang berbeda. (Misalnya, bilangan bulat Python pada dasarnya berukuran variabel.)

Mungkin alasan utama C ++ sangat kuat untuk tipe ukuran tetap adalah tujuan kompatibilitas C. Namun, karena C ++ adalah bahasa yang diketik secara statis yang mencoba menghasilkan kode yang sangat efisien, dan menghindari menambahkan hal-hal yang tidak ditentukan secara spesifik oleh pemrogram, tipe ukuran tetap masih membuat banyak akal.

Jadi mengapa C lebih memilih tipe ukuran tetap? Sederhana. Itu dirancang untuk menulis sistem operasi era 70-an, perangkat lunak server, dan utilitas; hal-hal yang menyediakan infrastruktur (seperti manajemen memori) untuk perangkat lunak lain. Pada level rendah seperti itu, kinerja sangat penting, dan begitu pula kompiler melakukan persis apa yang Anda katakan.


5

Untuk mengubah ukuran variabel akan membutuhkan realokasi dan ini biasanya tidak sebanding dengan siklus CPU tambahan dibandingkan dengan membuang beberapa byte memori lebih banyak.

Variabel lokal menggunakan tumpukan yang sangat cepat untuk dimanipulasi ketika variabel-variabel tersebut tidak berubah ukurannya. Jika Anda memutuskan untuk memperluas ukuran variabel dari 1 byte menjadi 2 byte, maka Anda harus memindahkan semua yang ada di stack dengan satu byte untuk membuat ruang itu. Itu berpotensi menghabiskan banyak siklus CPU tergantung pada berapa banyak hal yang perlu dipindahkan.

Cara lain yang bisa Anda lakukan adalah dengan membuat setiap variabel sebagai penunjuk ke lokasi tumpukan, tetapi sebenarnya Anda akan membuang lebih banyak siklus CPU dan memori dengan cara ini, sebenarnya. Pointer adalah 4 byte (pengalamatan 32 bit) atau 8 byte (pengalamatan 64 bit), jadi Anda sudah menggunakan 4 atau 8 untuk pointer, kemudian ukuran sebenarnya dari data pada heap. Masih ada biaya untuk realokasi dalam kasus ini. Jika Anda perlu merealokasi tumpukan data, Anda bisa beruntung dan memiliki ruang untuk memperluasnya secara inline, tetapi kadang-kadang Anda harus memindahkannya di tempat lain di tumpukan untuk memiliki blok memori yang berdekatan dari ukuran yang Anda inginkan.

Selalu lebih cepat untuk memutuskan berapa banyak memori yang digunakan sebelumnya. Jika Anda dapat menghindari ukuran dinamis, Anda mendapatkan kinerja. Memori yang terbuang biasanya sebanding dengan perolehan kinerja. Itu sebabnya komputer memiliki banyak memori. :)


3

Kompiler diizinkan untuk membuat banyak perubahan pada kode Anda, selama semuanya masih berfungsi (aturan "apa adanya").

Mungkin saja untuk menggunakan instruksi pemindahan literal 8-bit alih-alih lebih lama (32/64 bit) yang diperlukan untuk memindahkan full int. Namun, Anda perlu dua instruksi untuk menyelesaikan beban, karena Anda harus mengatur register menjadi nol terlebih dahulu sebelum melakukan beban.

Itu hanya lebih efisien (setidaknya sesuai dengan kompiler utama) untuk menangani nilai sebagai 32 bit. Sebenarnya, saya belum melihat kompiler x86 / x86_64 yang akan melakukan beban 8-bit tanpa perakitan inline.

Namun, semuanya berbeda ketika datang ke 64 bit. Saat merancang ekstensi sebelumnya (dari 16 hingga 32 bit) dari prosesor mereka, Intel melakukan kesalahan. Berikut ini adalah representasi yang baik dari penampilan mereka. Hal utama yang bisa diambil di sini adalah ketika Anda menulis ke AL ​​atau AH, yang lain tidak terpengaruh (cukup adil, itu intinya dan masuk akal saat itu). Tapi itu menjadi menarik ketika mereka mengembangkannya menjadi 32 bit. Jika Anda menulis bit bawah (AL, AH atau AX), tidak ada yang terjadi pada 16 bit atas EAX, yang berarti bahwa jika Anda ingin mempromosikan a charmenjadi int, Anda perlu menghapus memori itu terlebih dahulu, tetapi Anda tidak memiliki cara untuk sebenarnya hanya menggunakan 16 bit teratas ini, membuat "fitur" ini lebih menyebalkan daripada apa pun.

Sekarang dengan 64 bit, AMD melakukan pekerjaan yang jauh lebih baik. Jika Anda menyentuh apa pun di 32 bit yang lebih rendah, 32 bit bagian atas hanya diatur ke 0. Ini mengarah ke beberapa optimasi aktual yang dapat Anda lihat di godbolt ini. . Anda dapat melihat bahwa memuat sesuatu 8 bit atau 32 bit dilakukan dengan cara yang sama, tetapi ketika Anda menggunakan variabel 64 bit, kompiler menggunakan instruksi berbeda tergantung pada ukuran aktual literal Anda.

Jadi Anda bisa lihat di sini, kompiler benar-benar dapat mengubah ukuran sebenarnya dari variabel Anda di dalam CPU jika itu akan menghasilkan hasil yang sama, tetapi tidak masuk akal untuk melakukannya untuk jenis yang lebih kecil.


koreksi: as-if . Juga, saya tidak melihat bagaimana, jika beban / toko yang lebih pendek dapat digunakan, yang akan membebaskan byte lain untuk digunakan - yang tampaknya menjadi apa yang ditanyakan oleh OP: tidak hanya menghindari menyentuh memori yang tidak diperlukan oleh nilai saat ini, tetapi bisa mengetahui berapa byte untuk dibaca, dan untuk secara ajaib menggeser semua RAM saat runtime sehingga beberapa ide filosofis yang aneh tentang efisiensi ruang (apalagi biaya kinerja raksasa!) terpenuhi ... Hanya dengan mendapatkan petunjuk jejak yang lebih rendah dimenangkan 't memecahkan' itu. Apa yang perlu dilakukan CPU / OS yang begitu rumit sehingga menjawab pertanyaan dengan jelas IMO.
underscore_d

1
Anda tidak dapat benar-benar "menyimpan memori" dalam register. Kecuali jika Anda mencoba melakukan sesuatu yang aneh dengan menyalahgunakan AH dan AL, Anda tidak dapat memiliki beberapa nilai yang berbeda dalam register tujuan umum yang sama. Variabel lokal sering tetap dalam register dan tidak pernah pergi ke RAM jika tidak perlu.
meneldal
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.