Dari C ke Majelis

16

Misalkan kita memiliki potongan kode C berikut untuk avr-8bit:

int v1=1;
int v2=2;
v2=v2+v1;

Saya mengharapkan pembongkaran berikut

ldi r18, 1;
ldi r19, 2;
add r19, r18;

tapi setelah saya berlari:

avr-gcc -mmcu=atmega2560 Test.c -o Test.elf

dan

avr-objdump -S Test.elf > Test.lss

Saya mendapatkan pembongkaran berikut

    ldi r24, 0x01   ; 1
    ldi r25, 0x00   ; 0
    std Y+2, r25    ; 0x02
    std Y+1, r24    ; 0x01
    ldi r24, 0x02   ; 2
    ldi r25, 0x00   ; 0
    std Y+4, r25    ; 0x04
    std Y+3, r24    ; 0x03
    ldd r18, Y+3    ; 0x03
    ldd r19, Y+4    ; 0x04
    ldd r24, Y+1    ; 0x01
    ldd r25, Y+2    ; 0x02
    add r24, r18
    adc r25, r19
    std Y+4, r25    ; 0x04
    std Y+3, r24    ; 0x03

Adakah yang bisa membantu saya memahami hasil disassembler?

Sunting: Menggunakan char majelis menjadi:

ldi r24, 0x01
std Y+1, r24
ldi r24, 0x02
std Y+2, r24
ldd r25, Y+2
ldd r24, Y+1
add r24, r25
std Y+2, r24

Kapan ada instruksi std?

Kopi hitam
sumber

Jawaban:

20

Jawaban singkat: register Anda 8-bit dan nilainya 16-bit. Karena itu menangani mereka dalam dua bagian.

Jawaban panjang:

    ldi r24, 0x01   ; 1
    ldi r25, 0x00   ; 0

Simpan nilai 16-bit 1 dalam register 8-bit r24, r25.

    std Y+2, r25    ; 0x02
    std Y+1, r24    ; 0x01

Simpan di lokasi tumpukan Y + 1, Y + 2.

    ldi r24, 0x02   ; 2
    ldi r25, 0x00   ; 0

Simpan nilai 16-bit 2 dalam register 8-bit r24, r25.

    std Y+4, r25    ; 0x04
    std Y+3, r24    ; 0x03

Simpan di lokasi tumpukan Y + 3, Y + 4.

    ldd r18, Y+3    ; 0x03
    ldd r19, Y+4    ; 0x04
    ldd r24, Y+1    ; 0x01
    ldd r25, Y+2    ; 0x02

Salin kembali dari tumpukan ke (r18, r19) dan (r24, r25)

    add r24, r18
    adc r25, r19

Tambahkan (r18, r19) ke (r24, r25), termasuk carry pada penambahan kedua

    std Y+4, r25    ; 0x04
    std Y+3, r24    ; 0x03

Simpan kembali di tumpukan.

Untuk mendapatkan perakitan asli Anda, cobalah dua hal:

  • gunakan variabel "char"
  • gunakan opsi kompiler "-O2"

Sunting : alasan kompiler menyimpan variabel ke stack daripada menyimpannya di register adalah karena mereka disimpan dengan tipe penyimpanan "otomatis" default. Ini dapat mengoptimalkan mereka ke dalam register tetapi tidak harus, bahkan jika Anda mendeklarasikannya dengan kelas penyimpanan "register".

Meskipun ini bukan persyaratan ketat dari bahasa itu perilaku compiler normal. Jika pada suatu saat Anda mengambil alamat v1 maka alamat itu harus ditetapkan sebagai lokasi penyimpanan, dan disimpan kembali di sana setiap kali nilai "v1" berubah. Jadi untuk menyimpan pembukuan apakah v1 harus disimpan dalam register atau di stack, ia menyimpannya di stack dan memperlakukan setiap baris kode secara terpisah.

pjc50
sumber
Terima kasih! Lebih jelas sekarang! Silakan temukan edit saya di pertanyaan.
DarkCoffee
1
Lihat hasil edit saya. Coba -O2 juga. Mungkin -O3, meskipun itu dapat menghasilkan kode yang rusak.
pjc50
3
Banyak kode tertanam yang saya gunakan dengan mendefinisikan tipe tambahan yang spesifik untuk ukurannya, seperti "uint8, uint16, uint32" untuk int unsigned, misalnya. Dengan begitu Anda selalu tahu persis variabel apa yang Anda hadapi. Khususnya tertanam, ditandatangani, mengambang, "int" kecil dari ukuran / penandatanganan yang tidak ditentukan, semuanya akan dikenakan biaya siklus CPU terbaik dan menyebabkan bug serius paling buruk.
John U
Kompiler nyata berhenti berperilaku seperti ini sekitar 10-15 tahun yang lalu. Masalah alokasi register sebagian besar diselesaikan dan kompiler sangat ahli dalam hal itu. Mereka tahu persis kapan suatu variabel harus ada di stack, dan kapan itu bisa ada dalam register, apakah itu sepadan dengan usaha untuk memindahkannya, dan kapan melakukannya. Pembukuan dilakukan pada waktu kompilasi, dan kompiler sendiri memiliki memori gigabita. Pengecualian besar adalah mode debug, untuk alasan yang jelas, tetapi kemudian semuanya ada di stack.
MSalters
@ pjc50 -O3dapat menghasilkan kode yang rusak? [rujukan?] (dan tidak, kode C yang memanggil Perilaku Tidak Terdefinisi dan kemudian istirahat dengan beberapa pengaturan optimasi tidak masuk hitungan)
marcelm
4

Ketika saya menemukan beberapa contoh kode, saya akan membuat komentar saya sebagai jawaban - yang lain sudah menjelaskan masalahnya.

Banyak kode tertanam yang saya gunakan dengan mendefinisikan tipe tambahan yang spesifik untuk ukurannya, seperti "uint8, uint16, uint32" untuk int unsigned, misalnya. Dengan begitu Anda selalu tahu persis variabel apa yang Anda hadapi. Khususnya tertanam, ditandatangani, mengambang, "int" kecil dari ukuran / penandatanganan yang tidak ditentukan, semuanya akan dikenakan biaya siklus CPU terbaik dan menyebabkan bug serius paling buruk.

Inilah #define kami saat ini:

/*
 * Example - the basic data types from our embedded code
 */
typedef unsigned char       uint8;  /*  8 bits */
typedef unsigned short int  uint16; /* 16 bits */
typedef unsigned long int   uint32; /* 32 bits */

typedef char                int8;   /*  8 bits */
typedef short int           int16;  /* 16 bits */
typedef int                 int32;  /* 32 bits */

typedef volatile int8       vint8;  /*  8 bits */
typedef volatile int16      vint16; /* 16 bits */
typedef volatile int32      vint32; /* 32 bits */

typedef volatile uint8      vuint8;  /*  8 bits */
typedef volatile uint16     vuint16; /* 16 bits */
typedef volatile uint32     vuint32; /* 32 bits */
John U
sumber
3
Ide bagus; uint8_t dan teman-teman sekarang menjadi bagian dari standar: stackoverflow.com/questions/16937459/…
pjc50
Sangat berguna! Kami agak mewarisi mereka yang memiliki proyek C89, jadi ada baiknya mengetahui ada versi resmi.
John U
2

Kode C Anda menggunakan variabel integer 16bit (int). Kompiler tidak dapat membaca pikiran Anda, sehingga kompilasi persis apa yang ada di file sumber. Jadi, jika Anda ingin variabel 8bit, Anda harus menggunakan tipe masing-masing.

Alhasil Anda tetap akan bisa menyimpan nilai-nilai di memori (meski lebih simpel). Saya tidak begitu baik dalam C, tetapi IMHO, ada beberapa opsi untuk menetapkan variabel ke beberapa register, jika Anda ingin beberapa variabel berada di register bukan RAM. Sesuatu seperti:

register unsigned char VARNAME asm("r3");

Perhatikan bahwa tidak semua register tersedia untuk trik semacam itu.

Jadi kesimpulannya? Tuliskan program Anda dalam pertemuan. Mereka akan selalu lebih kecil, lebih cepat dan mudah dibaca / didukung.

Johnfound
sumber
Majelis lebih mudah dibaca daripada C?
dext0rb
@ dext0rb - Ya. Tentu saja jika Anda mengenal keduanya dengan cukup baik. Jika Anda hanya tahu C, maka bahasa assembly dan bahasa lainnya akan sulit dibaca.
johnfound
Saya harus tidak setuju dengan poin terakhir, program yang ditulis dengan assembler jauh lebih sulit dibaca. Bandingkan saja kode sumber yang diberikan di atas. Kode C jauh lebih jelas dan lebih pendek, serta niatnya. Perbedaan ini hanya tumbuh ketika struct digunakan.
soando
@soando - Kode C lebih pendek, ya. Lebih jelas? Saya tidak yakin. Jika demikian, pertanyaan di atas tidak perlu ditanyakan sama sekali. Sebenarnya harga "kekurangan" adalah "kabur" dari detail.
johnfound
Tentu saja, orang yang mengatakan "Aku tidak begitu baik dalam bahasa C" akan memproklamirkan kebajikan perakitan murni. : D
dext0rb