Ini ialah perintah perlpacktut yang boleh dijalankan dalam penyedia pengehosan percuma OnWorks menggunakan salah satu daripada berbilang stesen kerja dalam talian percuma kami seperti Ubuntu Online, Fedora Online, emulator dalam talian Windows atau emulator dalam talian MAC OS.
JADUAL:
NAMA
perlpacktut - tutorial tentang "pack" dan "unpack"
DESCRIPTION
"pack" dan "unpack" ialah dua fungsi untuk mengubah data mengikut yang ditentukan pengguna
templat, antara cara terjaga Perl menyimpan nilai dan beberapa perwakilan yang jelas
seperti yang mungkin diperlukan dalam persekitaran program Perl. Malangnya, mereka juga berdua
daripada fungsi yang paling tidak difahami dan paling kerap diabaikan yang disediakan oleh Perl. ini
tutorial akan menghilangkan misteri mereka untuk anda.
. Asas Prinsip
Kebanyakan bahasa pengaturcaraan tidak melindungi memori tempat pembolehubah disimpan. Dalam C, untuk
contohnya, anda boleh mengambil alamat beberapa pembolehubah, dan pengendali "sizeof" memberitahu anda
berapa banyak bait diperuntukkan kepada pembolehubah. Menggunakan alamat dan saiz, anda boleh
akses storan sesuka hati anda.
Dalam Perl, anda tidak boleh mengakses memori secara rawak, tetapi struktur dan perwakilan
penukaran yang disediakan oleh "pack" dan "unpack" adalah alternatif yang sangat baik. "pek"
fungsi menukar nilai kepada jujukan bait yang mengandungi perwakilan mengikut a
spesifikasi yang diberikan, hujah yang dipanggil "template". "membuka bungkusan" ialah proses terbalik,
memperoleh beberapa nilai daripada kandungan rentetan bait. (Awas, bagaimanapun, bahawa
tidak semua yang telah dibungkus bersama-sama boleh dibongkar dengan kemas - pengalaman yang sangat biasa seperti
pengembara berpengalaman mungkin mengesahkan.)
Mengapa, anda mungkin bertanya, adakah anda memerlukan sebahagian daripada memori yang mengandungi beberapa nilai dalam binari
perwakilan? Satu sebab yang baik ialah input dan output mengakses beberapa fail, peranti atau a
sambungan rangkaian, di mana perwakilan binari ini sama ada dipaksa ke atas anda atau kehendak
memberi anda sedikit faedah dalam pemprosesan. Sebab lain ialah menghantar data ke beberapa panggilan sistem
yang tidak tersedia sebagai fungsi Perl: "syscall" memerlukan anda menyediakan parameter
disimpan dalam cara ia berlaku dalam program C. Malah pemprosesan teks (seperti yang ditunjukkan dalam seterusnya
bahagian) boleh dipermudahkan dengan penggunaan bijak kedua-dua fungsi ini.
Untuk melihat cara (nyah) pembungkusan berfungsi, kami akan mulakan dengan kod templat mudah tempat penukaran
berada dalam gear rendah: antara kandungan urutan bait dan rentetan perenambelasan
digit. Mari gunakan "buka bungkusan", kerana ini mungkin mengingatkan anda tentang program pembuangan, atau beberapa
Mesej terakhir yang terdesak, program malang biasanya akan dilemparkan kepada anda sebelum tamat tempoh
ke dalam biru liar di sana. Dengan mengandaikan bahawa pembolehubah $mem memegang jujukan bait itu
kami ingin memeriksa tanpa mengandaikan apa-apa tentang maksudnya, kami boleh menulis
my( $hex ) = unpack( 'H*', $mem );
cetak "$hex\n";
maka kita mungkin melihat sesuatu seperti ini, dengan setiap pasangan digit heks sepadan dengan
satu bait:
41204d414e204120504c414e20412043414e414c2050414e414d41
Apa yang ada dalam cebisan ingatan ini? Nombor, aksara atau campuran kedua-duanya? Andainya
kita berada di komputer di mana pengekodan ASCII (atau beberapa yang serupa) digunakan: nilai perenambelasan dalam
julat 0x40 - 0x5A menunjukkan huruf besar dan 0x20 mengekodkan ruang. Jadi kita mungkin
menganggap ia adalah sekeping teks, yang sesetengahnya boleh membaca seperti tabloid; tetapi orang lain akan melakukannya
perlu memegang meja ASCII dan menghidupkan semula perasaan pelajar kelas satu itu. Tak kisah pun
banyak tentang cara membaca ini, kami perhatikan bahawa "membuka bungkusan" dengan kod templat "H"
menukarkan kandungan urutan bait kepada tatatanda perenambelasan biasa.
Oleh kerana "jujukan" ialah petunjuk kuantiti yang agak samar-samar, "H" telah ditakrifkan kepada
tukar hanya satu digit perenambelasan melainkan ia diikuti dengan kiraan ulangan. An
asterisk untuk kiraan ulangan bermaksud menggunakan apa sahaja yang tinggal.
Operasi songsang - mengemas kandungan bait daripada rentetan digit heksadesimal - ialah
sama mudah ditulis. Sebagai contoh:
my $s = pack( 'H2' x 10, 30..39 );
cetak "$s\n";
Memandangkan kami menyuap senarai sepuluh rentetan perenambelasan 2 digit untuk "mengemas", templat pek
hendaklah mengandungi sepuluh kod pek. Jika ini dijalankan pada komputer dengan pengekodan aksara ASCII,
ia akan mencetak 0123456789.
Pembungkusan teks
Katakan anda perlu membaca dalam fail data seperti ini:
Tarikh |Penerangan | Pendapatan|Perbelanjaan
01/24/2001 Zed's Camel Emporium 1147.99
01/28/2001 Semburan kutu 24.99
01/29/2001 Tunggangan unta kepada pelancong 235.00
Bagaimana kita melakukannya? Anda mungkin berfikir dahulu untuk menggunakan "split"; bagaimanapun, sejak "pecah" runtuh
medan kosong, anda tidak akan tahu sama ada rekod ialah pendapatan atau perbelanjaan. Aduh. baiklah,
anda sentiasa boleh menggunakan "substr":
manakala (<>) {
my $date = substr($_, 0, 11);
my $desc = substr($_, 12, 27);
my $income = substr($_, 40, 7);
my $expend = substr($_, 52, 7);
...
}
Ia tidak benar-benar satu tong ketawa, bukan? Malah, ia lebih teruk daripada yang kelihatan; yang
bermata helang mungkin menyedari bahawa medan pertama hendaklah hanya 10 aksara lebar, dan
ralat telah disebarkan terus melalui nombor lain - yang kami terpaksa mengira dengan tangan.
Jadi ia terdedah kepada kesilapan dan juga sangat tidak mesra.
Atau mungkin kita boleh menggunakan ungkapan biasa:
manakala (<>) {
my($tarikh, $desc, $income, $expend) =
m|(\d\d/\d\d/\d{4}) (.{27}) (.{7})(.*)|;
...
}
Urgh. Baiklah, ia lebih baik, tetapi - adakah anda mahu mengekalkannya?
Hei, bukankah Perl sepatutnya membuat perkara seperti ini mudah? Nah, ia boleh, jika anda menggunakan
alatan yang betul. "pack" dan "unpack" direka untuk membantu anda apabila berurusan dengan tetap-
data lebar seperti di atas. Mari kita lihat penyelesaian dengan "membongkar":
manakala (<>) {
my($date, $desc, $income, $expend) = unpack("A10xA27xA7A*", $_);
...
}
Itu kelihatan lebih bagus; tetapi kita perlu mengasingkan templat pelik itu. Mana saya tarik
daripada itu?
OK, mari kita lihat beberapa data kami sekali lagi; sebenarnya, kami akan memasukkan pengepala, dan a
pembaris yang berguna supaya kita boleh menjejaki di mana kita berada.
1 2 3 4 5
1234567890123456789012345678901234567890123456789012345678
Tarikh |Penerangan | Pendapatan|Perbelanjaan
01/28/2001 Semburan kutu 24.99
01/29/2001 Tunggangan unta kepada pelancong 235.00
Daripada ini, kita dapat melihat bahawa lajur tarikh terbentang dari lajur 1 hingga lajur 10 - sepuluh
aksara lebar. "Pek"-ese untuk "watak" ialah "A", dan sepuluh daripadanya ialah "A10". Jadi kalau
kami hanya mahu mengekstrak tarikh, kami boleh mengatakan ini:
my($date) = unpack("A10", $_);
OK, apa seterusnya? Antara tarikh dan huraian ialah lajur kosong; kita nak ponteng
atas itu. Templat "x" bermaksud "langkau ke hadapan", jadi kami mahukan salah satu daripadanya. Seterusnya, kita ada
kumpulan aksara lain, dari 12 hingga 38. Itu 27 aksara lagi, jadi "A27". (Jangan
buat ralat tiang pagar - terdapat 27 aksara antara 12 dan 38, bukan 26. Kira mereka!)
Sekarang kita melangkau watak lain dan mengambil 7 aksara seterusnya:
my($tarikh,$penerangan,$pendapatan) = unpack("A10xA27xA7", $_);
Sekarang datang sedikit yang bijak. Garisan dalam lejar kita yang hanya pendapatan dan bukan perbelanjaan
mungkin berakhir di lajur 46. Oleh itu, kami tidak mahu memberitahu corak "membongkar" kami bahawa kami perlu kepada
cari lagi 12 aksara; kita hanya akan berkata "jika ada apa-apa lagi, ambil". Seperti awak
mungkin meneka daripada ungkapan biasa, itulah maksud "*": "gunakan segala-galanya
baki".
· Berhati-hati, bagaimanapun, bahawa tidak seperti ungkapan biasa, jika templat "buka bungkusan" tidak
sepadan dengan data masuk, Perl akan menjerit dan mati.
Oleh itu, meletakkan semuanya bersama-sama:
saya ($tarikh, $perihalan, $pendapatan, $belanja) =
unpack("A10xA27xA7xA*", $_);
Sekarang, itulah data kami yang dihuraikan. Saya rasa apa yang kita mungkin mahu lakukan sekarang ialah jumlah pendapatan kita
dan perbelanjaan, dan tambah satu lagi baris pada penghujung lejar kami - dalam format yang sama -
mengatakan berapa banyak yang telah kami bawa masuk dan berapa banyak yang telah kami belanjakan:
manakala (<>) {
my ($tarikh, $desc, $income, $expend) =
unpack("A10xA27xA7xA*", $_);
$tot_pendapatan += $pendapatan;
$tot_belanja += $belanja;
}
$tot_income = sprintf("%.2f", $tot_income); # Masukkan mereka
$tot_belanja = sprintf("%.2f", $tot_belanja); # format "kewangan".
$date = POSIX::strftime("%m/%d/%Y", localtime);
# Ok mari kita pergi:
pek cetak("A10xA27xA7xA*", $tarikh, "Jumlah",
$tot_income, $tot_expend);
Oh, hmm. Itu tidak begitu berkesan. Mari lihat apa yang berlaku:
01/24/2001 Zed's Camel Emporium 1147.99
01/28/2001 Semburan kutu 24.99
01/29/2001 Tunggangan unta kepada pelancong 1235.00
03/23/2001Jumlah 1235.001172.98
OK, ini adalah permulaan, tetapi apa yang berlaku kepada ruang? Kami meletakkan "x", bukan? Bukankah sepatutnya
lompat ke hadapan? Mari lihat apa yang dikatakan "pek" dalam perlfunc:
x Satu bait nol.
Urgh. Patutlah. Terdapat perbezaan besar antara "bait nol", aksara sifar dan "a
space", watak 32. Perl meletakkan sesuatu di antara tarikh dan penerangan - tetapi
malangnya, kami tidak dapat melihatnya!
Apa yang sebenarnya kita perlu lakukan ialah mengembangkan lebar medan. Format "A" melapisi mana-mana
aksara yang tidak wujud dengan ruang, jadi kami boleh menggunakan ruang tambahan untuk membariskan kami
bidang, seperti ini:
pek cetak("A11 A28 A8 A*", $tarikh, "Jumlah",
$tot_income, $tot_expend);
(Perhatikan bahawa anda boleh meletakkan ruang dalam templat untuk menjadikannya lebih mudah dibaca, tetapi tidak
terjemah ke ruang dalam output.) Inilah yang kami dapat kali ini:
01/24/2001 Zed's Camel Emporium 1147.99
01/28/2001 Semburan kutu 24.99
01/29/2001 Tunggangan unta kepada pelancong 1235.00
03/23/2001 Jumlah 1235.00 1172.98
Itu lebih baik sedikit, tetapi kami masih mempunyai lajur terakhir yang perlu dialihkan lebih jauh
habis. Terdapat cara mudah untuk menyelesaikan masalah ini: malangnya, kami tidak dapat "mengemas" ke kanan-
mewajarkan bidang kami, tetapi kami boleh mendapatkan "sprintf" untuk melakukannya:
$tot_income = sprintf("%.2f", $tot_income);
$tot_expend = sprintf("%12.2f", $tot_expend);
$date = POSIX::strftime("%m/%d/%Y", localtime);
pek cetak("A11 A28 A8 A*", $tarikh, "Jumlah",
$tot_income, $tot_expend);
Kali ini kita mendapat jawapan yang betul:
01/28/2001 Semburan kutu 24.99
01/29/2001 Tunggangan unta kepada pelancong 1235.00
03/23/2001 Jumlah 1235.00 1172.98
Jadi begitulah cara kami menggunakan dan menghasilkan data lebar tetap. Mari kita imbas kembali apa yang telah kita lihat
"pack" dan "unpack" setakat ini:
· Gunakan "pek" untuk beralih daripada beberapa keping data kepada satu versi lebar tetap; gunakan "buka bungkusan"
untuk menukar rentetan format lebar tetap kepada beberapa keping data.
· Format pek "A" bermaksud "sebarang aksara"; jika anda "mengemas" dan anda telah kehabisan
perkara yang perlu dikemas, "bungkus" akan mengisi yang lain dengan ruang.
· "x" bermaksud "langkau satu bait" apabila "membuka bungkusan"; apabila "mengemas", ia bermaksud "memperkenalkan null
byte" - itu mungkin bukan maksud anda jika anda berurusan dengan teks biasa.
· Anda boleh mengikut format dengan nombor untuk menyatakan bilangan aksara yang akan terjejas
mengikut format itu: "A12" bermaksud "ambil 12 aksara"; "x6" bermaksud "langkau 6 bait" atau
"watak 0, 6 kali".
· Daripada nombor, anda boleh menggunakan "*" untuk bermaksud "makan semua yang tinggal".
Amaran: apabila membungkus berbilang keping data, "*" hanya bermaksud "mengambil semua
sekeping data semasa". Maksudnya
pek("A*A*", $satu, $dua)
bungkus semua $satu ke dalam "A*" pertama dan kemudian semua $dua ke dalam kedua. Ini adalah
prinsip umum: setiap aksara format sepadan dengan satu keping data yang akan
"pek" ed.
Pembungkusan nombor
Begitu banyak untuk data teks. Mari kita beralih kepada bahan daging yang "bungkus" dan "buka bungkus" adalah yang terbaik
di: mengendalikan format binari untuk nombor. Terdapat, sudah tentu, bukan hanya satu format binari
- kehidupan akan menjadi terlalu mudah - tetapi Perl akan melakukan semua kerja yang rumit untuk anda.
Integer
Nombor pembungkusan dan pembongkaran membayangkan penukaran kepada dan dari beberapa khusus perduaan
perwakilan. Meninggalkan nombor titik terapung diketepikan buat masa ini, yang menonjol
sifat mana-mana perwakilan tersebut ialah:
· bilangan bait yang digunakan untuk menyimpan integer,
· sama ada kandungan ditafsirkan sebagai nombor yang ditandatangani atau tidak ditandatangani,
· susunan bait: sama ada bait pertama ialah bait terkecil atau paling ketara (atau:
little-endian atau big-endian, masing-masing).
Jadi, sebagai contoh, untuk membungkus 20302 kepada integer 16 bit yang ditandatangani dalam komputer anda
perwakilan yang anda tulis
my $ps = pack( 's', 20302 );
Sekali lagi, hasilnya ialah rentetan, kini mengandungi 2 bait. Jika anda mencetak rentetan ini (iaitu,
secara amnya, tidak disyorkan) anda mungkin melihat "HIDUP" atau "TIDAK" (bergantung pada bait sistem anda
pesanan) - atau sesuatu yang sama sekali berbeza jika komputer anda tidak menggunakan aksara ASCII
pengekodan. Membongkar $ps dengan templat yang sama mengembalikan nilai integer asal:
my( $s ) = unpack( 's', $ps );
Ini benar untuk semua kod templat berangka. Tetapi jangan mengharapkan keajaiban: jika dibungkus
nilai melebihi kapasiti bait yang diperuntukkan, bit tertib tinggi dibuang secara senyap, dan
membongkar sudah tentu tidak akan dapat menarik mereka kembali daripada beberapa topi ajaib. Dan, apabila anda mengemas
menggunakan kod templat yang ditandatangani seperti "s", nilai lebihan boleh mengakibatkan bit tanda
menetapkan, dan membongkar ini akan mengembalikan nilai negatif dengan bijak.
16 bit tidak akan membawa anda terlalu jauh dengan integer, tetapi terdapat "l" dan "L" untuk ditandatangani dan
integer 32-bit yang tidak ditandatangani. Dan jika ini tidak mencukupi dan sistem anda menyokong 64 bit
integer anda boleh menolak had lebih dekat kepada infiniti dengan kod pek "q" dan "Q". A
pengecualian ketara disediakan oleh kod pek "i" dan "I" untuk integer yang ditandatangani dan tidak ditandatangani
daripada pelbagai "adat tempatan": Integer sedemikian akan mengambil sebanyak bait sebagai C tempatan
pengkompil kembali untuk "sizeof(int)", tetapi ia akan digunakan at kurangnya 32 bit.
Setiap kod pek integer "sSlLqQ" menghasilkan bilangan bait yang tetap, tidak kira
tempat anda melaksanakan program anda. Ini mungkin berguna untuk sesetengah aplikasi, tetapi tidak
menyediakan cara mudah alih untuk menghantar struktur data antara program Perl dan C (terikat kepada
berlaku apabila anda memanggil sambungan XS atau fungsi Perl "syscall"), atau apabila anda membaca atau
menulis fail binari. Perkara yang anda perlukan dalam kes ini ialah kod templat yang bergantung pada apa
pengkompil C tempatan anda menyusun apabila anda kod "pendek" atau "panjang tidak ditandatangani", contohnya.
Kod ini dan panjang bait yang sepadan ditunjukkan dalam jadual di bawah. Sejak
C standard meninggalkan banyak kelonggaran berkenaan dengan saiz relatif jenis data ini,
nilai sebenar mungkin berbeza-beza, dan itulah sebabnya nilai diberikan sebagai ungkapan dalam C dan Perl.
(Jika anda ingin menggunakan nilai daripada %Config dalam program anda, anda perlu mengimportnya dengan "use
Konfigurasi".)
panjang bait yang tidak ditandatangani ditandatangani dalam panjang bait C dalam Perl
s! S! sizeof(short) $Config{shortsize}
i! saya! sizeof(int) $Config{intsize}
l! L! sizeof(long) $Config{longsize}
q! Q! sizeof(long long) $Config{longlongsize}
"i!" dan saya!" kod tidak berbeza daripada "i" dan "I"; mereka bertolak ansur untuk
demi kesempurnaan.
Membongkar a Menumpukkan Frame
Meminta pesanan bait tertentu mungkin diperlukan apabila anda bekerja dengan data binari
datang dari beberapa seni bina khusus manakala program anda boleh berjalan sepenuhnya
sistem yang berbeza. Sebagai contoh, anggap anda mempunyai 24 bait yang mengandungi bingkai tindanan seperti itu
berlaku pada Intel 8086:
+---------+ +----+----+ +---------+
TOS: | IP | TOS+4:| FL | FH | BENDERA TOS+14:| SI |
+---------+ +----+----+ +---------+
| CS | | AL | AH | AX | DI |
+---------+ +----+----+ +---------+
| BL | BH | BX | BP |
+----+----+ +---------+
| CL | CH | CX | DS |
+----+----+ +---------+
| DL | DH | DX | ES |
+----+----+ +---------+
Pertama, kami ambil perhatian bahawa CPU 16-bit yang dihormati masa ini menggunakan susunan little-endian, dan itulah sebabnya
bait tertib rendah disimpan di alamat yang lebih rendah. Untuk membongkar pendek (tidak ditandatangani) sedemikian, kami akan
kena guna kod "v". Kiraan ulangan membongkar semua 12 seluar pendek:
my( $ip, $cs, $bendera, $ax, $bx, $cd, $dx, $si, $di, $bp, $ds, $es ) =
unpack( 'v12', $frame );
Sebagai alternatif, kami boleh menggunakan "C" untuk membongkar daftar bait yang boleh diakses secara individu
FL, FH, AL, AH, dsb.:
my( $fl, $fh, $al, $ah, $bl, $bh, $cl, $ch, $dl, $dh ) =
unpack( 'C10', substr( $frame, 4, 10 ) );
Alangkah baiknya jika kita dapat melakukan ini dalam satu masa: buka bungkusan pendek, sandarkan sedikit,
dan kemudian bongkar 2 bait. Sejak Perl is bagus, ia menawarkan kod templat "X" untuk disandarkan
satu bait. Menyatukan ini semua, kita kini boleh menulis:
saya( $ip, $cs,
$bendera,$fl,$fh,
$ax,$al,$ah, $bx,$bl,$bh, $cx,$cl,$ch, $dx,$dl,$dh,
$si, $di, $bp, $ds, $es ) =
unpack( 'v2' . ('vXXCC' x 5) . 'v5', $frame );
(Pembinaan templat yang kekok boleh dielakkan - teruskan membaca!)
Kami telah berusaha keras untuk membina templat supaya ia sepadan dengan kandungan kami
penampan bingkai. Jika tidak, kami sama ada akan mendapat nilai yang tidak ditentukan atau "membuka bungkusan" tidak dapat membongkar
semua. Jika "pek" kehabisan item, ia akan membekalkan rentetan nol (yang dipaksa masuk
sifar setiap kali kod pek berkata demikian).
Cara kepada Makan an Telur on a Bersih
Kod pek untuk big-endian (bait pesanan tinggi pada alamat terendah) ialah "n" untuk 16 bit dan
"N" untuk integer 32 bit. Anda menggunakan kod ini jika anda tahu bahawa data anda berasal dari a
seni bina yang mematuhi, tetapi, cukup mengejutkan, anda juga harus menggunakan kod pek ini jika
anda bertukar-tukar data binari, merentasi rangkaian, dengan beberapa sistem yang anda tahu di sebelah
apa-apa tentang. Sebab mudah ialah pesanan ini telah dipilih sebagai rangkaian bagi,
dan semua program ketakutan standard harus mengikuti konvensyen ini. (Ini, sudah tentu, a
sokongan tegas untuk salah satu parti Lilliputian dan mungkin mempengaruhi politik
pembangunan di sana.) Jadi, jika protokol mengharapkan anda menghantar mesej dengan menghantar
panjang dahulu, diikuti dengan begitu banyak bait, anda boleh menulis:
my $buf = pack( 'N', length( $msg ) ) . $msg;
atau bahkan:
my $buf = pack( 'NA*', length( $msg ), $msg );
dan lulus $buf ke rutin penghantaran anda. Sesetengah protokol menuntut bahawa kiraan harus disertakan
panjang kiraan itu sendiri: kemudian tambahkan 4 pada panjang data. (Tetapi pastikan anda membaca
"Panjang dan Lebar" sebelum anda benar-benar kod ini!)
Pesanan bait pengubah
Dalam bahagian sebelumnya, kami telah mempelajari cara menggunakan "n", "N", "v" dan "V" untuk mengemas dan membongkar
integer dengan tertib bait endian besar atau kecil. Walaupun ini bagus, ia masih agak
terhad kerana ia meninggalkan semua jenis integer yang ditandatangani serta integer 64-bit. Untuk
contoh, jika anda ingin membongkar jujukan integer 16-bit big-endian yang ditandatangani dalam
cara bebas platform, anda perlu menulis:
my @data = unpack 's*', pack 'S*', unpack 'n*', $buf;
Ini adalah hodoh. Mulai Perl 5.9.2, terdapat cara yang lebih baik untuk menyatakan keinginan anda untuk a
tertib bait tertentu: pengubah ">" dan "<". ">" ialah pengubah suai big-endian, manakala "<"
ialah pengubah suai kecil-endian. Menggunakannya, kita boleh menulis semula kod di atas sebagai:
my @data = unpack 's>*', $buf;
Seperti yang anda lihat, "hujung besar" anak panah menyentuh "s", yang merupakan cara yang baik untuk
ingat bahawa ">" ialah pengubah suai big-endian. Perkara yang sama jelas berfungsi untuk "<", di mana
"penghujung kecil" menyentuh kod.
Anda mungkin akan mendapati pengubah suai ini lebih berguna jika anda perlu berurusan dengan besar-atau
struktur C little-endian. Pastikan anda membaca "Membungkus dan Membongkar Struktur C" untuk mendapatkan maklumat lanjut
pada itu.
Terapung titik nombor
Untuk membungkus nombor titik terapung anda mempunyai pilihan antara kod pek "f", "d",
"F" dan "D". "f" dan "d" bungkus ke dalam (atau buka bungkusan dari) ketepatan tunggal atau ketepatan dua kali
perwakilan seperti yang disediakan oleh sistem anda. Jika sistem anda menyokongnya, "D" boleh jadi
digunakan untuk mengemas dan membongkar nilai ("ganda panjang"), yang boleh menawarkan lebih banyak resolusi daripada
"f" atau "d". Nota Bahawa terdapat adalah berbeza lama dua kali ganda format.
"F" memuatkan "NV", iaitu jenis titik terapung yang digunakan oleh Perl secara dalaman.
Tiada perkara seperti perwakilan rangkaian untuk reals, jadi jika anda ingin menghantar anda
nombor nyata merentasi sempadan komputer, lebih baik anda berpegang pada perwakilan teks,
mungkin menggunakan format apungan heksadesimal (mengelakkan kehilangan penukaran perpuluhan), melainkan
anda benar-benar pasti apa yang ada di hujung talian yang lain. Untuk lebih banyak lagi
adventuresome, anda boleh menggunakan pengubah tertib bait dari bahagian sebelumnya juga pada
kod titik terapung.
Exotic Templates
Bit Rentetan
Bit adalah atom dalam dunia ingatan. Akses kepada bit individu mungkin perlu digunakan
sama ada sebagai pilihan terakhir atau kerana ia adalah cara paling mudah untuk mengendalikan data anda. sedikit
rentetan (nyah)pembungkusan menukar antara rentetan yang mengandungi siri 0 dan 1 aksara dan
urutan bait setiap satu mengandungi kumpulan 8 bit. Ini hampir semudah itu
bunyi, kecuali terdapat dua cara kandungan bait boleh ditulis sebagai sedikit
tali. Mari kita lihat bait beranotasi:
7 6 5 4 3 2 1 0
+-----------------+
| 1 0 0 0 1 1 0 0 |
+-----------------+
MSB LSB
Ia makan telur sekali lagi: Ada yang berpendapat bahawa sebagai rentetan sedikit ini harus ditulis
"10001100" iaitu bermula dengan bit yang paling ketara, yang lain menegaskan "00110001".
Nah, Perl tidak berat sebelah, jadi itulah sebabnya kami mempunyai dua kod rentetan bit:
$byte = pack( 'B8', '10001100' ); # mulakan dengan MSB
$byte = pack( 'b8', '00110001' ); # mulakan dengan LSB
Tidak mungkin untuk membungkus atau membongkar medan bit - hanya bait integral. "bungkus" sentiasa
bermula pada sempadan bait seterusnya dan "membundarkan" kepada gandaan 8 seterusnya dengan menambah sifar
bit mengikut keperluan. (Jika anda mahu medan bit, terdapat "vec" dalam perlfunc. Atau anda boleh
melaksanakan pengendalian medan bit pada tahap rentetan aksara, menggunakan split, substr, dan
penggabungan pada rentetan bit yang tidak dibungkus.)
Untuk menggambarkan pembongkaran rentetan bit, kami akan menguraikan daftar status mudah ("-"
bermaksud bit "terpelihara"):
+-------------------------------------+
| SZ - A - P - C | - - - - ODIT |
+-------------------------------------+
MSB LSB MSB LSB
Menukar dua bait ini kepada rentetan boleh dilakukan dengan templat unpack 'b16'. Kepada
dapatkan nilai bit individu daripada rentetan bit yang kami gunakan "split" dengan "kosong"
corak pemisah yang membedah kepada aksara individu. Nilai bit daripada
jawatan "terpelihara" hanya diberikan kepada "undef", notasi yang mudah untuk "Saya tidak
peduli ke mana ini pergi".
($carry, undef, $parity, undef, $auxcarry, undef, $zero, $sign,
$jejak, $gangguan, $arah, $limpahan) =
split( //, unpack( 'b16', $status ) );
Kami juga boleh menggunakan templat unpack 'b12', kerana 4 bit terakhir boleh
tidak diendahkan pula.
Uuencoding
Satu lagi ganjil-man-out dalam abjad templat ialah "u", yang memuatkan "rentetan kod uue".
("uu" ialah singkatan untuk Unix-to-Unix.) Kemungkinan anda tidak akan memerlukan pengekodan ini.
teknik yang dicipta untuk mengatasi kelemahan transmisi lama
medium yang tidak menyokong selain daripada data ASCII mudah. Resipi penting adalah mudah:
Ambil tiga bait, atau 24 bit. Bahagikannya kepada 4 enam pek, tambahkan ruang (0x20) pada setiap satu.
Ulang sehingga semua data sebati. Lipat kumpulan 4 bait ke dalam baris tidak lebih daripada
60 dan hiaskannya di hadapan dengan kiraan bait asal (ditambah sebanyak 0x20) dan "\n"
pada penghujungnya. - Chef "pek" akan menyediakan ini untuk anda, sebentar lagi, apabila anda memilih pek
kod "u" pada menu:
my $uubuf = pack( 'u', $bindat );
Kiraan ulangan selepas "u" menetapkan bilangan bait untuk dimasukkan ke dalam baris uuencoded, iaitu
maksimum 45 secara lalai, tetapi boleh ditetapkan kepada beberapa (lebih kecil) gandaan integer bagi
tiga. "membuka bungkusan" hanya mengabaikan kiraan ulangan.
melakukan Jumlah
Kod templat yang lebih asing ialah "%"nombor>. Pertama, kerana ia digunakan sebagai awalan kepada
beberapa kod templat lain. Kedua, kerana ia tidak boleh digunakan dalam "pek" sama sekali, dan ketiga,
dalam "unpack", tidak mengembalikan data seperti yang ditakrifkan oleh kod templat yang didahuluinya. Sebaliknya
ia akan memberi anda integer daripada nombor bit yang dikira daripada nilai data dengan melakukan
jumlah. Untuk kod unpack numerik, tiada pencapaian besar dicapai:
my $buf = pack( 'iii', 100, 20, 3 );
print unpack( '%32i3', $buf ), "\n"; # cetakan 123
Untuk nilai rentetan, "%" mengembalikan jumlah nilai bait yang menjimatkan masalah jumlah
gelung dengan "substr" dan "ord":
print unpack( '%32A*', "\x01\x10" ), "\n"; # cetakan 17
Walaupun kod "%" didokumenkan sebagai mengembalikan "checksum": jangan meletakkan kepercayaan anda
nilai sedemikian! Walaupun apabila digunakan pada sebilangan kecil bait, ia tidak akan menjamin a
jarak Hamming yang ketara.
Berhubung dengan "b" atau "B", "%" hanya menambah bit, dan ini boleh digunakan dengan baik untuk
mengira bit set dengan cekap:
my $bitcount = unpack( '%32b*', $mask );
Dan bit pariti genap boleh ditentukan seperti ini:
my $evenparity = unpack( '%1b*', $mask );
Unicode
Unicode ialah set aksara yang boleh mewakili kebanyakan aksara dalam kebanyakan aksara dunia
bahasa, menyediakan ruang untuk lebih satu juta aksara yang berbeza. Unicode 3.1 menentukan
94,140 aksara: Aksara Latin Asas diberikan kepada nombor 0 - 127.
Latin-1 Tambahan dengan aksara yang digunakan dalam beberapa bahasa Eropah adalah dalam
julat seterusnya, sehingga 255. Selepas beberapa sambungan Latin lagi, kami dapati set aksara daripada
bahasa menggunakan abjad bukan Rom, diselangi dengan pelbagai set simbol seperti
simbol mata wang, Zapf Dingbats atau Braille. (Anda mungkin mahu melawat
<http://www.unicode.org/> untuk melihat sebahagian daripadanya - kegemaran peribadi saya ialah Telugu
dan Kannada.)
Set aksara Unicode mengaitkan aksara dengan integer. Pengekodan nombor ini dalam
bilangan bait yang sama akan lebih daripada dua kali ganda keperluan untuk menyimpan teks yang ditulis
dalam abjad Latin. Pengekodan UTF-8 mengelakkan ini dengan menyimpan yang paling biasa (dari a
sudut pandang barat) aksara dalam satu bait manakala mengekodkan yang jarang berlaku dalam tiga
atau lebih bait.
Perl menggunakan UTF-8, secara dalaman, untuk kebanyakan rentetan Unicode.
Jadi apa kaitannya dengan "pek"? Nah, jika anda ingin mengarang rentetan Unicode
(yang dikodkan secara dalaman sebagai UTF-8), anda boleh melakukannya dengan menggunakan kod templat "U". Sebagai sebuah
contoh, mari kita hasilkan simbol mata wang Euro (nombor kod 0x20AC):
$UTF8{Euro} = pek( 'U', 0x20AC );
# Bersamaan dengan: $UTF8{Euro} = "\x{20ac}";
Memeriksa $UTF8{Euro} menunjukkan bahawa ia mengandungi 3 bait: "\xe2\x82\xac". Walau bagaimanapun, ia
mengandungi hanya 1 aksara, nombor 0x20AC. Perjalanan pergi balik boleh diselesaikan dengan "membuka bungkusan":
$Unicode{Euro} = unpack( 'U', $UTF8{Euro} );
Membongkar menggunakan kod templat "U" juga berfungsi pada rentetan bait yang dikodkan UTF-8.
Biasanya anda ingin mengemas atau membongkar rentetan UTF-8:
# bungkus dan bongkar abjad Ibrani
my $alefbet = pack( 'U*', 0x05d0..0x05ea );
my @hebrew = unpack( 'U*', $utf );
Sila ambil perhatian: dalam kes umum, anda lebih baik menggunakan Encode::decode_utf8 untuk menyahkod
rentetan bait yang dikodkan UTF-8 kepada rentetan Unicode Perl dan Encode::encode_utf8 untuk mengekod
Rentetan Perl Unicode kepada UTF-8 bait. Fungsi ini menyediakan cara untuk mengendalikan bait yang tidak sah
urutan dan umumnya mempunyai antara muka yang lebih mesra.
Satu lagi Portable Perduaan Pengekodan
Kod pek "w" telah ditambahkan untuk menyokong skim pengekodan data binari mudah alih yang
melangkaui integer mudah. (Butiran boleh didapati dihttp://Casbah.org/>, Scarab
projek.) BER (Perwakilan Dikodkan Perduaan) pangkalan stor integer tidak bertanda yang dimampatkan
128 digit, digit paling penting dahulu, dengan seberapa sedikit digit yang mungkin. Bit lapan (the
bit tinggi) ditetapkan pada setiap bait kecuali yang terakhir. Tiada had saiz untuk pengekodan BER, tetapi
Perl tidak akan melampau.
my $berbuf = pack( 'w*', 1, 128, 128+1, 128*128+127 );
Lambakan heks $berbuf, dengan ruang disisipkan di tempat yang betul, menunjukkan 01 8100 8101
81807F. Oleh kerana bait terakhir sentiasa kurang daripada 128, "membuka bungkusan" tahu di mana hendak berhenti.
Templat Pengumpulan
Sebelum Perl 5.8, pengulangan templat perlu dibuat dengan "x"-pendaraban
rentetan templat. Kini terdapat cara yang lebih baik kerana kita mungkin menggunakan kod pek "(" dan ")"
digabungkan dengan kiraan ulangan. Templat "bongkar" daripada contoh Stack Frame boleh
hanya ditulis seperti ini:
unpack( 'v2 (vXXCC)5 v5', $frame )
Mari kita terokai ciri ini sedikit lagi. Kita akan mulakan dengan yang setara dengan
sertai( '', peta( substr( $_, 0, 1 ), @str ) )
yang mengembalikan rentetan yang terdiri daripada aksara pertama daripada setiap rentetan. Menggunakan pek, kami
boleh menulis
pek( '(A)'.@str, @str )
atau, kerana kiraan ulangan "*" bermaksud "ulang sekerap yang diperlukan", ringkasnya
pek( '(A)*', @str )
(Perhatikan bahawa templat "A*" hanya akan membungkus $str[0] dengan panjang penuh.)
Untuk membungkus tarikh yang disimpan sebagai tiga kali ganda ( hari, bulan, tahun ) dalam tatasusunan @tarikh ke dalam urutan
daripada bait, bait, integer pendek yang boleh kita tulis
$pd = pek( '(CCS)*', peta( @$_, @tarikh ) );
Untuk menukar pasangan aksara dalam rentetan (dengan panjang genap) seseorang boleh menggunakan beberapa
teknik. Mula-mula, mari kita gunakan "x" dan "X" untuk melangkau ke hadapan dan ke belakang:
$s = pack( '(A)*', unpack( '(xAXXAx)*', $s ) );
Kita juga boleh menggunakan "@" untuk melompat ke ofset, dengan 0 ialah kedudukan di mana kita berada semasa
terakhir "(" ditemui:
$s = pack( '(A)*', unpack( '(@1A @0A @2)*', $s ) );
Akhir sekali, terdapat juga pendekatan yang sama sekali berbeza dengan membongkar seluar pendek endian besar dan
membungkusnya dalam susunan bait terbalik:
$s = pack( '(v)*', unpack( '(n)*', $s );
Panjang and Lebar
Rentetan Panjang
Dalam bahagian sebelumnya kita telah melihat mesej rangkaian yang telah dibina dengan memberi awalan
panjang mesej binari kepada mesej sebenar. Anda akan mendapati bahawa pembungkusan panjang diikuti dengan
begitu banyak bait data ialah resipi yang kerap digunakan kerana menambahkan bait nol tidak akan berfungsi
jika bait nol mungkin menjadi sebahagian daripada data. Berikut adalah contoh di mana kedua-dua teknik digunakan:
selepas dua rentetan yang ditamatkan nol dengan alamat sumber dan destinasi, Mesej Ringkas (kepada
telefon bimbit) dihantar selepas bait panjang:
my $msg = pack( 'Z*Z*CA*', $src, $dst, length( $sm ), $sm );
Membongkar mesej ini boleh dilakukan dengan templat yang sama:
($src, $dst, $len, $sm ) = unpack( 'Z*Z*CA*', $msg );
Terdapat perangkap halus yang mengintai dalam masa terdekat: Menambah medan lain selepas Mesej Ringkas
(dalam pembolehubah $sm) tidak mengapa apabila mengemas, tetapi ini tidak boleh dibongkar secara naif:
# bungkus mesej
my $msg = pack( 'Z*Z*CA*C', $src, $dst, length( $sm ), $sm, $prio );
# unpack gagal - $prio kekal tidak ditentukan!
($src, $dst, $len, $sm, $prio ) = unpack( 'Z*Z*CA*C', $msg );
Kod pek "A*" memakan semua bait yang tinggal dan $prio kekal tidak ditentukan! Sebelum kita
biarkan kekecewaan melemahkan semangat: Perl mempunyai kad truf untuk membuat helah ini juga,
cuma jauh ke atas lengan baju. Tengok ini:
# bungkus mesej: ASCIIZ, ASCIIZ, panjang/rentetan, bait
$msg saya = pek( 'Z* Z* C/A* C', $src, $dst, $sm, $prio );
# buka bungkusan
( $src, $dst, $sm, $prio ) = unpack( 'Z* Z* C/A* C', $msg );
Menggabungkan dua kod pek dengan garis miring ("/") mengaitkannya dengan satu nilai daripada
senarai hujah. Dalam "pack", panjang hujah diambil dan dibungkus mengikut
kod pertama manakala hujah itu sendiri ditambah selepas ditukar dengan kod templat
selepas garis miring. Ini menjimatkan masalah kita untuk memasukkan panggilan "panjang", tetapi ia sudah masuk
"unpack" di mana kita benar-benar mendapat markah: Nilai bait panjang menandakan penghujung rentetan
untuk diambil dari penimbal. Oleh kerana gabungan ini tidak masuk akal kecuali apabila
kod pek kedua bukan "a*", "A*" atau "Z*", Perl tidak akan membenarkan anda.
Kod pek sebelum "/" mungkin apa-apa sahaja yang sesuai untuk mewakili nombor: Semua
kod pek binari angka, dan juga kod teks seperti "A4" atau "Z*":
# bungkus/bongkar rentetan yang didahului dengan panjangnya dalam ASCII
my $buf = pack( 'A4/A*', "Humpty-Dumpty" );
# unpack $buf: '13 Humpty-Dumpty'
my $txt = unpack( 'A4/A*', $buf );
"/" tidak dilaksanakan dalam Perls sebelum 5.6, jadi jika kod anda diperlukan untuk berfungsi pada yang lebih lama
Perls anda perlu "membongkar( 'Z* Z* C')" untuk mendapatkan panjang, kemudian gunakannya untuk membuat yang baharu
buka rentetan. Sebagai contoh
# bungkus mesej: ASCIIZ, ASCIIZ, panjang, rentetan, bait
# (5.005 serasi)
$msg saya = pek( 'Z* Z* CA* C', $src, $dst, panjang $sm, $sm, $prio );
# buka bungkusan
( undef, undef, $len) = unpack( 'Z* Z* C', $msg );
($src, $dst, $sm, $prio) = unpack ( "Z* Z* x A$len C", $msg );
Tetapi "membongkar" kedua itu sedang bergegas ke hadapan. Ia tidak menggunakan rentetan literal yang mudah untuk
templat. Jadi mungkin kita patut perkenalkan...
Dinamik Templates
Setakat ini, kami telah melihat literal digunakan sebagai templat. Jika senarai item pek tidak ada
panjang tetap, ungkapan yang membina templat diperlukan (bila-bila masa, untuk sesetengah orang
sebab, "()*" tidak boleh digunakan). Berikut ialah contoh: Untuk menyimpan nilai rentetan bernama dengan cara
yang boleh dihuraikan dengan mudah oleh program C, kami mencipta urutan nama dan nol
ditamatkan rentetan ASCII, dengan "=" antara nama dan nilai, diikuti dengan
bait nol pembatas tambahan. Ini caranya:
my $env = pack( '(A*A*Z*)' . kekunci( %Env ) . 'C',
peta( {( $_, '=', $Env{$_} ) } kekunci( %Env ) ), 0 );
Mari kita periksa roda pengisar bait ini, satu demi satu. Terdapat panggilan "peta", mencipta
item yang kami ingin masukkan ke dalam penimbal $env: pada setiap kunci (dalam $_) ia menambahkan "="
pemisah dan nilai masukan cincang. Setiap triplet dibungkus dengan kod templat
urutan "A*A*Z*" yang diulang mengikut bilangan kekunci. (Ya, itulah yang
fungsi "kunci" kembali dalam konteks skalar.) Untuk mendapatkan bait nol terakhir, kami menambah 0 pada
penghujung senarai "pek", untuk dibungkus dengan "C". (Pembaca yang penuh perhatian mungkin perasan
bahawa kita boleh meninggalkan 0.)
Untuk operasi terbalik, kita perlu menentukan bilangan item dalam penimbal
sebelum kita boleh membiarkan "membongkar" merobeknya:
$n saya = $env =~ tr/\0// - 1;
my %env = map( split( /=/, $_ ), unpack( "(Z*)$n", $env ) );
"tr" mengira bait nol. Panggilan "buka bungkus" mengembalikan senarai pasangan nilai nama setiap satu
yang dipisahkan dalam blok "peta".
Mengira Ulangan
Daripada menyimpan sentinel di hujung item data (atau senarai item), kami boleh
mendahului data dengan kiraan. Sekali lagi, kami membungkus kunci dan nilai cincang, sebelum setiap satu
dengan kiraan panjang pendek yang tidak ditandatangani, dan di hadapan kami menyimpan bilangan pasangan:
my $env = pack( 'S(S/A* S/A*)*', kunci skalar( %Env ), %Env );
Ini memudahkan operasi terbalik kerana bilangan ulangan boleh dibongkar
kod "/":
my %env = unpack( 'S/(S/A* S/A*)', $env );
Ambil perhatian bahawa ini adalah salah satu kes yang jarang berlaku di mana anda tidak boleh menggunakan templat yang sama untuk "pek"
dan "buka bungkus" kerana "pek" tidak dapat menentukan kiraan ulangan untuk kumpulan "()".
Intel HEX
Intel HEX ialah format fail untuk mewakili data binari, kebanyakannya untuk pelbagai pengaturcaraan
cip, sebagai fail teks. (Lihathttp://en.wikipedia.org/wiki/.hex> untuk detail
penerangan, danhttp://en.wikipedia.org/wiki/SREC_(format_fail)> untuk Motorola
Format rekod S, yang boleh dirungkai menggunakan teknik yang sama.) Setiap baris bermula dengan
titik bertindih (':') dan diikuti dengan jujukan aksara perenambelasan, menyatakan bait
mengira n (8 bit), alamat (16 bit, endian besar), jenis rekod (8 bit), n bait data dan
jumlah semak (8 bit) dikira sebagai bait paling tidak ketara daripada jumlah pelengkap kedua-duanya
bait sebelumnya. Contoh: ":0300300002337A1E".
Langkah pertama pemprosesan garisan sedemikian ialah penukaran, kepada binari, perenambelasan
data, untuk mendapatkan empat medan, sambil menyemak jumlah semak. Tiada kejutan di sini: kami akan
mulakan dengan panggilan "pek" mudah untuk menukar segala-galanya kepada binari:
my $binrec = pack( 'H*', substr( $hexrec, 1 ) );
Urutan bait yang terhasil adalah paling mudah untuk menyemak jumlah semak. Jangan perlahankan anda
atur atur cara dengan gelung for menambah nilai "ord" bagi bait rentetan ini - "buka bungkus"
kod "%" ialah perkara yang perlu digunakan untuk mengira jumlah 8-bit semua bait, yang mesti sama
kepada sifar:
mati melainkan unpack( "%8C*", $binrec ) == 0;
Akhirnya, mari dapatkan empat bidang itu. Pada masa ini, anda sepatutnya tidak mempunyai sebarang masalah dengan
tiga medan pertama - tetapi bagaimana kita boleh menggunakan kiraan bait data dalam medan pertama sebagai a
panjang untuk medan data? Di sini kod "x" dan "X" datang untuk menyelamatkan, seperti yang dibenarkan
melompat ke sana ke mari dalam tali untuk membongkar.
my( $addr, $type, $data ) = unpack( "xn C X4 C x3 /a", $bin );
Kod "x" melangkau bait, kerana kita tidak memerlukan kiraan lagi. Kod "n" menjaga
Alamat integer big-endian 16-bit dan "C" membongkar jenis rekod. Berada di offset 4,
di mana data bermula, kita memerlukan kiraan. "X4" membawa kita kembali ke titik pertama, iaitu
bait pada offset 0. Sekarang kita mengambil kiraan, dan zum ke hadapan untuk mengimbangi 4, di mana kita berada sekarang
dilengkapi sepenuhnya untuk mengekstrak bilangan tepat bait data, meninggalkan jumlah semak mengekor
bait sahaja.
Pembungkusan and Membongkar C Struktur
Dalam bahagian sebelumnya kita telah melihat cara mengemas nombor dan rentetan aksara. Jika ia
bukan untuk beberapa halangan kita boleh menyimpulkan bahagian ini dengan segera dengan teguran ringkas
bahawa struktur C tidak mengandungi apa-apa lagi, dan oleh itu anda sudah tahu semua yang ada
kepadanya. Maaf, tidak: baca terus, sila.
Jika anda perlu berurusan dengan banyak struktur C, dan tidak mahu menggodam semua templat anda
rentetan secara manual, anda mungkin ingin melihat modul CPAN
"Tukar:: Perduaan:: C". Ia bukan sahaja boleh menghuraikan sumber C anda secara langsung, tetapi ia juga telah membina-
sebagai sokongan untuk semua kemungkinan dan pengakhiran yang diterangkan lebih lanjut dalam bahagian ini.
. Penjajaran Pit
Dalam pertimbangan kelajuan terhadap keperluan memori, baki telah dicondongkan ke dalam
memihak kepada pelaksanaan yang lebih cepat. Ini telah mempengaruhi cara pengkompil C memperuntukkan memori
struktur: Pada seni bina di mana operan 16-bit atau 32-bit boleh dialihkan dengan lebih pantas antara
tempat dalam ingatan, atau kepada atau dari daftar CPU, jika ia sejajar pada genap atau berbilang-
daripada empat atau bahkan pada berbilang daripada lapan alamat, pengkompil C akan memberikan anda kelajuan ini
mendapat manfaat dengan memasukkan bait tambahan ke dalam struktur. Jika anda tidak menyeberangi pantai C ini
tidak mungkin menyebabkan anda kesedihan (walaupun anda harus mengambil berat apabila anda mereka bentuk data yang besar
struktur, atau anda mahu kod anda mudah alih antara seni bina (anda mahu itu,
bukan?)).
Untuk melihat cara ini mempengaruhi "pek" dan "buka bungkus", kami akan membandingkan dua struktur C ini:
typedef struct {
char c1;
seluar pendek;
char c2;
panjang l;
} gappy_t;
typedef struct {
panjang l;
seluar pendek;
char c1;
char c2;
} padat_t;
Biasanya, pengkompil C memperuntukkan 12 bait kepada pembolehubah "gappy_t", tetapi hanya memerlukan 8
bait untuk "dense_t". Selepas menyiasat perkara ini dengan lebih lanjut, kita boleh melukis peta memori, menunjukkan
di mana 4 bait tambahan disembunyikan:
0 +4 +8 +12
+--+--+--+--+--+--+--+--+--+--+--+--+
|c1|xx| s |c2|xx|xx|xx| l | xx = bait isian
+--+--+--+--+--+--+--+--+--+--+--+--+
gappy_t
0 +4 +8
+--+--+--+--+--+--+--+--+
| l | h |c1|c2|
+--+--+--+--+--+--+--+--+
padat_t
Dan di situlah quirk pertama menyerang: templat "pack" dan "unpack" perlu diisi
dengan kod "x" untuk mendapatkan bait isian tambahan tersebut.
Soalan semula jadi: "Mengapa Perl tidak boleh mengimbangi jurang?" menjamin jawapan. satu
sebab yang baik ialah pengkompil C mungkin menyediakan sambungan (bukan ANSI) yang membenarkan semua jenis
kawalan mewah ke atas cara struktur diselaraskan, walaupun pada tahap individu
bidang struktur. Dan, jika ini tidak mencukupi, terdapat perkara berbahaya yang dipanggil "kesatuan"
di mana jumlah bait isian tidak boleh diperoleh daripada penjajaran item seterusnya
sahaja.
OK, jadi mari kita gigit peluru. Berikut ialah satu cara untuk membetulkan penjajaran dengan memasukkan
kod templat "x", yang tidak mengambil item yang sepadan daripada senarai:
my $gappy = pack( 'cxs cxxx l!', $c1, $s, $c2, $l );
Perhatikan "!" selepas "l": Kami ingin memastikan bahawa kami membungkus integer panjang semasa ia disusun
oleh pengkompil C kami. Dan walaupun sekarang, ia hanya akan berfungsi untuk platform di mana pengkompil
menyelaraskan perkara seperti di atas. Dan seseorang di suatu tempat mempunyai platform di mana ia tidak.
[Mungkin Cray, di mana "pendek", "int" dan "panjang" semuanya 8 bait. :-)]
Mengira bait dan melihat penjajaran dalam struktur yang panjang pasti menjadi seretan. bukan
adakah cara kita boleh mencipta templat dengan program mudah? Berikut ialah program C yang berfungsi
muslihatnya:
#sertakan
#termasuk
typedef struct {
char fc1;
fs pendek;
char fc2;
fl panjang;
} gappy_t;
#define Pt(struct,field,tchar) \
printf( "@%d%s", offsetof(struct,field), # tchar );
int utama () {
Pt( gappy_t, fc1, c );
Pt( gappy_t, fs, s! );
Pt( gappy_t, fc2, c );
Pt( gappy_t, fl, l! );
printf("\n");
}
Barisan keluaran boleh digunakan sebagai templat dalam panggilan "pek" atau "buka bungkus":
my $gappy = pack( '@0c @2s! @4c @8l!', $c1, $s, $c2, $l );
Wah, satu lagi kod templat - seolah-olah kami tidak mempunyai banyak. Tetapi "@" menyelamatkan hari kita dengan mendayakan
kami untuk menentukan offset dari permulaan penimbal pek ke item seterusnya: Ini adalah
hanya nilai makro "offsetof" (ditakrifkan dalam " ") kembali apabila diberi a
jenis "struct" dan salah satu daripada nama medannya ("ahli-penentu" dalam C standardese).
Tidak menggunakan offset mahupun menambah "x" untuk merapatkan jurang adalah memuaskan. (Cuba bayangkan
apa yang berlaku jika struktur berubah.) Apa yang kita perlukan adalah cara untuk mengatakan "langkau sebagai
banyak bait seperti yang diperlukan untuk gandaan N seterusnya". Dalam Templatese yang fasih, anda menyebut ini
dengan "x!N" di mana N digantikan dengan nilai yang sesuai. Berikut ialah versi kami yang seterusnya
pembungkusan struktur:
my $gappy = pack( 'cx!2 scx!4 l!', $c1, $s, $c2, $l );
Itu sudah tentu lebih baik, tetapi kita masih perlu tahu berapa lama semua integer, dan
mudah alih adalah jauh. Daripada 2, sebagai contoh, kami ingin mengatakan "walaupun panjang pendek
adalah". Tetapi ini boleh dilakukan dengan melampirkan kod pek yang sesuai dalam kurungan: "[s]". Jadi,
inilah yang terbaik yang boleh kita lakukan:
my $gappy = pack( 'cx![s] scx![l!] l!', $c1, $s, $c2, $l );
Berurusan bersama Endian-ness
Sekarang, bayangkan bahawa kami ingin membungkus data untuk mesin dengan pesanan bait yang berbeza.
Pertama, kita perlu memikirkan berapa besar jenis data pada mesin sasaran sebenarnya.
Mari kita andaikan bahawa panjang adalah 32 bit lebar dan pendek adalah 16 bit lebar. Anda boleh kemudian
tulis semula templat sebagai:
my $gappy = pack( 'cx![s] scx![l] l', $c1, $s, $c2, $l );
Jika mesin sasaran adalah little-endian, kita boleh menulis:
my $gappy = pack( 'cx![s] s< cx![l] l<', $c1, $s, $c2, $l );
Ini memaksa ahli pendek dan panjang menjadi little-endian, dan tidak mengapa jika anda
tidak mempunyai terlalu ramai ahli struct. Tetapi kita juga boleh menggunakan pengubah suai pesanan bait pada a
kumpulan dan tulis perkara berikut:
my $gappy = pack( '( cx![s] scx![l] l )<', $c1, $s, $c2, $l );
Ini tidak sesingkat sebelumnya, tetapi ia menjadikannya lebih jelas bahawa kami berhasrat untuk melakukannya
pesanan bait little-endian untuk keseluruhan kumpulan, bukan sahaja untuk kod templat individu. Ia boleh
juga lebih mudah dibaca dan lebih mudah diselenggara.
penjajaran, Mengambil 2
Saya khuatir kita masih belum selesai dengan tangkapan penjajaran. Hidra meningkat
satu lagi kepala hodoh apabila anda mengemas susunan struktur:
typedef struct {
kiraan pendek;
glyph char;
} sel_t;
typedef cell_t buffer_t[BUFLEN];
mana tangkapannya? Padding tidak diperlukan sebelum medan pertama "kiraan", mahupun antara
ini dan medan "glyph" seterusnya, jadi mengapa kita tidak boleh pek seperti ini:
# ada yang tidak kena di sini:
pek( 's!a' x @buffer,
peta{ ($_->{count}, $_->{glyph} ) } @buffer );
Ini memuatkan "3*@buffer" bait, tetapi ternyata saiz "buffer_t" ialah empat kali
"BUFLEN"! Moral cerita ialah penjajaran yang diperlukan bagi struktur atau tatasusunan adalah
disebarkan ke peringkat yang lebih tinggi seterusnya di mana kita perlu mempertimbangkan padding at yang akhir setiap
komponen juga. Oleh itu templat yang betul ialah:
pek( 's!ax' x @buffer,
peta{ ($_->{count}, $_->{glyph} ) } @buffer );
penjajaran, Mengambil 3
Dan walaupun anda mengambil kira semua perkara di atas, ANSI masih membenarkan ini:
typedef struct {
char foo[2];
} foo_t;
berbeza dalam saiz. Kekangan penjajaran struktur boleh lebih besar daripada mana-mana strukturnya
elemen. [Dan jika anda berpendapat bahawa ini tidak menjejaskan perkara biasa, potong yang seterusnya
telefon bimbit yang anda lihat. Banyak yang mempunyai teras ARM, dan peraturan struktur ARM membuat "sizeof
(foo_t)" == 4]
petunjuk khususnya Cara kepada Penggunaan Mereka
Tajuk bahagian ini menunjukkan masalah kedua yang mungkin anda hadapi lambat laun
apabila anda mengemas struktur C. Jika fungsi yang anda ingin panggil menjangkakan, katakan, "void *"
nilai, awak tidak boleh hanya mengambil rujukan kepada pembolehubah Perl. (Walaupun nilai itu
sudah tentu adalah alamat memori, ia bukan alamat di mana kandungan pembolehubah berada
disimpan.)
Kod templat "P" berjanji untuk membungkus "penunjuk kepada rentetan panjang tetap". Bukankah ini apa
kami mahu? Mari kita cuba:
# peruntukkan beberapa storan dan bungkus penunjuk kepadanya
$memori saya = "\x00" x $saiz;
my $memptr = pack( 'P', $memory );
Tetapi tunggu: bukankah "pek" hanya mengembalikan urutan bait? Bagaimana kita boleh melepasi rentetan ini
bait kepada beberapa kod C menjangkakan penunjuk yang, selepas semua, tiada apa-apa selain nombor? The
jawapannya mudah: Kita perlu mendapatkan alamat berangka daripada bait yang dikembalikan oleh "pek".
my $ptr = unpack( 'L!', $memptr );
Jelas sekali ini mengandaikan bahawa adalah mungkin untuk menaip penuding kepada panjang yang tidak ditandatangani dan
sebaliknya, yang sering berfungsi tetapi tidak boleh dianggap sebagai undang-undang sejagat. - Sekarang itu
kita mempunyai penunjuk ini soalan seterusnya ialah: Bagaimana kita boleh menggunakannya dengan baik? Kami memerlukan panggilan
ke beberapa fungsi C di mana penunjuk dijangka. The membaca(2) panggilan sistem terlintas di fikiran:
ssize_t read(int fd, void *buf, size_t count);
Selepas membaca perlfunc menerangkan cara menggunakan "syscall" kita boleh menulis fungsi Perl ini
menyalin fail ke output standard:
memerlukan 'syscall.ph'; # jalankan h2ph untuk menjana fail ini
sub kucing($){
$path saya = shift();
$size saya = -s $path;
$memori saya = "\x00" x $saiz; # peruntukkan sedikit ingatan
my $ptr = unpack( 'L', pack( 'P', $memory ) );
buka( F, $path ) || die("$path: cannot open ($!)\n" );
$fd saya = fileno(F);
my $res = syscall( &SYS_read, fileno(F), $ptr, $size );
cetak $memori;
tutup( F );
}
Ini bukan contoh kesederhanaan mahupun contoh mudah alih tetapi ia menggambarkan
intinya: Kami dapat menyelinap di sebalik tabir dan mengakses Perl yang dikawal dengan baik
ingatan! (Nota penting: "syscall" Perl tidak memerlukan anda untuk membina penunjuk masuk
jalan bulatan ini. Anda hanya lulus pembolehubah rentetan, dan Perl memajukan alamat.)
Bagaimanakah "membongkar" dengan "P" berfungsi? Bayangkan beberapa penunjuk dalam penimbal yang akan dibongkar:
Jika ia bukan penunjuk nol (yang akan menghasilkan nilai "undef" dengan bijak) kita mempunyai
alamat mula - tetapi kemudian apa? Perl tidak mempunyai cara untuk mengetahui berapa lama "panjang tetap ini
string" adalah, jadi terpulang kepada anda untuk menentukan saiz sebenar sebagai panjang eksplisit selepas "P".
my $mem = "abcdefghijklmn";
print unpack( 'P5', pack( 'P', $mem ) ); # cetakan "abcde"
Akibatnya, "pek" mengabaikan sebarang nombor atau "*" selepas "P".
Sekarang kita telah melihat "P" di tempat kerja, kita juga boleh memberikan "p" pusingan. Mengapa kita memerlukan a
kod templat kedua untuk pembungkusan petunjuk sama sekali? Jawapannya terletak di sebalik fakta yang mudah
bahawa "bongkar" dengan "p" menjanjikan rentetan yang ditamatkan nol bermula pada alamat yang diambil
daripada penimbal, dan itu membayangkan panjang untuk item data dikembalikan:
my $buf = pack( 'p', "abc\x00efhijklmn" );
print unpack( 'p', $buf ); # cetakan "abc"
Walaupun ini mungkin mengelirukan: Akibat panjang yang tersirat oleh
panjang rentetan, nombor selepas kod pek "p" ialah kiraan ulangan, bukan panjang seperti selepasnya
"P".
Menggunakan "pack(..., $x)" dengan "P" atau "p" untuk mendapatkan alamat tempat $x sebenarnya disimpan mesti
digunakan dengan teliti. Jentera dalaman Perl mempertimbangkan hubungan antara a
pembolehubah dan alamat itu sebagai perkara peribadinya sendiri dan tidak begitu peduli bahawa kita
telah mendapat salinan. Oleh itu:
· Jangan gunakan "pek" dengan "p" atau "P" untuk mendapatkan alamat pembolehubah yang akan pergi
di luar skop (dan dengan itu membebaskan ingatannya) sebelum anda selesai menggunakan
ingatan di alamat itu.
· Berhati-hati dengan operasi Perl yang mengubah nilai pembolehubah. Melampirkan
sesuatu kepada pembolehubah, sebagai contoh, mungkin memerlukan pengagihan semula storannya,
meninggalkan anda dengan penunjuk ke tanah tanpa manusia.
· Jangan fikir anda boleh mendapatkan alamat pembolehubah Perl apabila ia disimpan sebagai
integer atau nombor berganda! "pack('P', $x)" akan memaksa dalaman pembolehubah
perwakilan kepada rentetan, sama seperti anda telah menulis sesuatu seperti "$x .= ''".
Walau bagaimanapun, selamat untuk P- atau p-pack rentetan literal, kerana Perl hanya memperuntukkan satu
pembolehubah tanpa nama.
Pek Resipi
Berikut ialah koleksi (mungkin) resipi dalam tin yang berguna untuk "bungkus" dan "buka bungkus":
# Tukar alamat IP untuk fungsi soket
pek( "C4", belah /\./, "123.4.5.6" );
# Kira bit dalam sebahagian memori (cth vektor pilih)
unpack( '%32b*', $mask );
# Tentukan endian sistem anda
$is_little_endian = unpack( 'c', pack( 's', 1 ) );
$is_big_endian = unpack( 'xc', pack( 's', 1 ) );
# Tentukan bilangan bit dalam integer asli
$bits = unpack( '%32I!', ~0 );
# Sediakan hujah untuk panggilan sistem nanosleep
my $timespec = pack( 'L!L!', $secs, $nanosecs );
Untuk longgokan memori yang mudah, kami membongkar beberapa bait ke dalam pasangan digit heks yang sama banyak, dan
gunakan "peta" untuk mengendalikan jarak tradisional - 16 bait ke baris:
$i saya;
cetak peta(++$i % 16 ? "$_ " : "$_\n",
buka bungkus( 'H2' x panjang( $mem ), $mem ) ),
panjang( $mem ) % 16 ? "\n" : '';
kelakar seksyen
# Menarik digit entah dari mana...
print unpack( 'C', pack( 'x' ) ),
buka bungkus( '%B*', bungkus( 'A' ) ),
buka bungkus( 'H', bungkus( 'A' ) ),
unpack( 'A', unpack( 'C', pack( 'A' ) ) ), "\n";
# Satu untuk jalan raya ;-)
my $advice = pack( 'semua yang anda boleh dalam van' );
Pengarang
Simon Cozens dan Wolfgang Laun.
Gunakan perlpacktut dalam talian menggunakan perkhidmatan onworks.net