Rumah > pembangunan bahagian belakang > Golang > Menguasai Penunjuk dalam Go: Meningkatkan Keselamatan, Prestasi dan Kebolehselenggaraan Kod

Menguasai Penunjuk dalam Go: Meningkatkan Keselamatan, Prestasi dan Kebolehselenggaraan Kod

DDD
Lepaskan: 2025-01-13 07:51:40
asal
472 orang telah melayarinya

Petunjuk dalam bahasa Go: alat berkuasa untuk operasi data dan pengurusan memori yang cekap

Penunjuk dalam bahasa Go menyediakan pembangun alat yang berkuasa untuk mengakses dan memanipulasi alamat memori pembolehubah secara terus. Tidak seperti pembolehubah tradisional, yang menyimpan nilai data sebenar, penunjuk menyimpan lokasi memori di mana nilai tersebut berada. Ciri unik ini membolehkan penunjuk mengubah suai data asal dalam ingatan, menyediakan kaedah pemprosesan data yang cekap dan pengoptimuman prestasi program.

Alamat memori diwakili dalam format perenambelasan (cth., 0xAFFFF) dan merupakan asas untuk penunjuk. Apabila anda mengisytiharkan pembolehubah penunjuk, ia pada asasnya pembolehubah khas yang memegang alamat memori pembolehubah lain, bukannya data itu sendiri.

Contohnya, penunjuk p dalam bahasa Go mengandungi rujukan 0x0001, yang secara langsung menghala ke alamat memori pembolehubah x yang lain. Hubungan ini membolehkan p berinteraksi secara langsung dengan nilai x, menunjukkan kuasa dan kegunaan penunjuk dalam bahasa Go.

Berikut ialah perwakilan visual cara penunjuk berfungsi:

Mastering Pointers in Go: Enhancing Safety, Performance, and Code Maintainability

Terokai petunjuk dalam bahasa Go

Untuk mengisytiharkan penunjuk dalam bahasa Go, sintaksnya ialah var p *T, dengan T mewakili jenis pembolehubah yang akan dirujuk oleh penuding. Pertimbangkan contoh berikut, di mana p ialah penunjuk kepada pembolehubah int:

<code class="language-go">var a int = 10
var p *int = &a</code>
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Di sini, p menyimpan alamat a, dan melalui penyahrujukan penunjuk (*p), nilai a boleh diakses atau diubah suai. Mekanisme ini adalah asas untuk manipulasi data dan pengurusan memori yang cekap dalam bahasa Go.

Mari kita lihat contoh asas:

<code class="language-go">func main() {
    x := 42
    p := &x
    fmt.Printf("x: %v\n", x)
    fmt.Printf("&x: %v\n", &x)
    fmt.Printf("p: %v\n", p)
    fmt.Printf("*p: %v\n", *p)

    pp := &p
    fmt.Printf("**pp: %v\n", **pp)
}</code>
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Output

<code>Value of x: 42
Address of x: 0xc000012120
Value stored in p: 0xc000012120
Value at the address p: 42
**pp: 42</code>
Salin selepas log masuk
Salin selepas log masuk

Penunjuk dalam bahasa Go berbeza daripada penunjuk dalam C/C

Salah faham biasa tentang masa untuk menggunakan penunjuk dalam Go berpunca daripada membandingkan penunjuk dalam Go secara terus dengan penunjuk dalam C. Memahami perbezaan antara kedua-duanya membolehkan anda memahami cara penunjuk berfungsi dalam setiap ekosistem bahasa. Mari selami perbezaan ini:

  • Tiada aritmetik penunjuk

Tidak seperti bahasa C, aritmetik penunjuk dalam bahasa C membenarkan manipulasi langsung alamat memori, manakala bahasa Go tidak menyokong aritmetik penunjuk. Pilihan reka bentuk yang disengajakan bagi bahasa Go ini membawa kepada beberapa kelebihan ketara:

  1. Cegah kelemahan limpahan penimbal: Dengan menghapuskan aritmetik penunjuk, bahasa Go secara asasnya mengurangkan risiko kelemahan limpahan penimbal, yang merupakan masalah keselamatan biasa dalam program bahasa C. Membenarkan penyerang melaksanakan kod arbitrari.
  2. Jadikan kod lebih selamat dan lebih mudah diselenggara: Tanpa kerumitan operasi memori langsung, kod bahasa Go lebih mudah difahami, lebih selamat dan lebih mudah diselenggara. Pembangun boleh menumpukan pada logik aplikasi dan bukannya kerumitan pengurusan memori.
  3. Kurangkan ralat berkaitan memori: Menghapuskan aritmetik penuding meminimumkan perangkap biasa seperti kebocoran memori dan segfault, menjadikan program Go lebih teguh dan stabil.
  4. Pengumpulan sampah yang dipermudahkan: Pendekatan bahasa Go terhadap petunjuk dan pengurusan memori memudahkan pengumpulan sampah kerana pengkompil dan masa jalan mempunyai pemahaman yang lebih jelas tentang kitaran hayat objek dan corak penggunaan memori. Penyederhanaan ini membawa kepada kutipan sampah yang lebih cekap, sekali gus meningkatkan prestasi.

Dengan menghapuskan aritmetik penunjuk, bahasa Go menghalang penyalahgunaan penunjuk, menghasilkan kod yang lebih dipercayai dan lebih mudah untuk diselenggara.

  • Pengurusan memori dan penunjuk berjuntai

Dalam bahasa Go, pengurusan memori jauh lebih mudah berbanding dalam bahasa seperti C kerana pengumpul sampahnya.

<code class="language-go">var a int = 10
var p *int = &a</code>
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
  1. Tidak perlu peruntukan/pelepasan memori manual: Bahasa Go mengabstrak kerumitan peruntukan memori dan deallocation melalui pengumpul sampahnya, memudahkan pengaturcaraan dan meminimumkan ralat.
  2. Tiada penunjuk berjuntai: Penunjuk berjuntai ialah penuding yang berlaku apabila alamat memori yang dirujuk oleh penuding dibebaskan atau diperuntukkan semula tanpa mengemas kini penuding. Penunjuk berjuntai ialah sumber ralat biasa dalam sistem pengurusan memori manual. Pengumpul sampah Go memastikan objek hanya dibersihkan apabila tiada rujukan sedia ada kepadanya, dengan berkesan menghalang penunjuk berjuntai.
  3. Cegah kebocoran memori: Kebocoran memori, selalunya disebabkan oleh terlupa untuk melepaskan memori yang tidak diperlukan lagi, telah dikurangkan dengan ketara dalam bahasa Go. Semasa dalam Go, objek dengan penuding boleh dicapai tidak dibebaskan, mengelakkan kebocoran akibat rujukan hilang, dalam C, pengaturcara mesti rajin mengurus memori secara manual untuk mengelakkan masalah sedemikian.
  • Tingkah laku penunjuk nol

Dalam bahasa Go, cuba menyahrujuk penuding nol akan menyebabkan panik. Tingkah laku ini memerlukan pembangun untuk mengendalikan semua situasi rujukan nol yang mungkin dan mengelakkan pengubahsuaian yang tidak disengajakan. Walaupun ini boleh meningkatkan overhed penyelenggaraan kod dan penyahpepijatan, ia juga boleh berfungsi sebagai langkah keselamatan terhadap jenis ralat tertentu:

<code class="language-go">func main() {
    x := 42
    p := &x
    fmt.Printf("x: %v\n", x)
    fmt.Printf("&x: %v\n", &x)
    fmt.Printf("p: %v\n", p)
    fmt.Printf("*p: %v\n", *p)

    pp := &p
    fmt.Printf("**pp: %v\n", **pp)
}</code>
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Output menunjukkan panik kerana alamat memori yang tidak sah atau nyahrujukan penunjuk nol:

<code>Value of x: 42
Address of x: 0xc000012120
Value stored in p: 0xc000012120
Value at the address p: 42
**pp: 42</code>
Salin selepas log masuk
Salin selepas log masuk

Oleh kerana pelajar ialah penunjuk nol dan tidak dikaitkan dengan mana-mana alamat memori yang sah, cuba mengakses medannya (Nama dan Umur) akan menyebabkan panik masa jalan.

Sebaliknya, dalam bahasa C, membatalkan rujukan penunjuk nol dianggap tidak selamat. Penunjuk yang tidak dimulakan dalam C menunjukkan kepada bahagian memori rawak (tidak ditentukan), yang menjadikannya lebih berbahaya. Menyahrujuk penuding yang tidak ditentukan sedemikian boleh bermakna bahawa atur cara terus berjalan dengan data yang rosak, membawa kepada tingkah laku yang tidak dapat diramalkan, rasuah data atau hasil yang lebih teruk lagi.

Pendekatan ini sememangnya mempunyai pertukaran - ia menghasilkan pengkompil Go yang lebih kompleks daripada pengkompil C. Akibatnya, kerumitan ini kadangkala boleh menjadikan program Go kelihatan lebih perlahan daripada program C mereka.

  • Salah tanggapan biasa: “Penunjuk sentiasa lebih laju”

Kepercayaan umum ialah memanfaatkan penunjuk boleh meningkatkan kelajuan aplikasi dengan meminimumkan salinan data. Konsep ini berpunca daripada seni bina Go sebagai bahasa kutipan sampah. Apabila penuding dihantar ke fungsi, bahasa Go menjalankan analisis melarikan diri untuk menentukan sama ada pembolehubah yang berkaitan harus berada pada tindanan atau diperuntukkan pada timbunan. Walaupun penting, proses ini memperkenalkan tahap overhed. Selain itu, jika keputusan analisis memutuskan untuk memperuntukkan timbunan untuk pembolehubah, lebih banyak masa akan digunakan dalam kitaran kutipan sampah (GC). Dinamik ini menggambarkan bahawa walaupun penunjuk mengurangkan salinan data langsung, kesannya terhadap prestasi adalah halus dan dipengaruhi oleh mekanisme asas pengurusan memori dan pengumpulan sampah dalam bahasa Go.

Analisis Melarikan Diri

Bahasa Go menggunakan analisis melarikan diri untuk menentukan julat nilai dinamik dalam persekitarannya. Proses ini merupakan sebahagian daripada cara bahasa Go mengurus peruntukan dan pengoptimuman memori. Matlamat terasnya adalah untuk memperuntukkan nilai Go dalam bingkai tindanan fungsi apabila boleh. Pengkompil Go mengambil tugas untuk menentukan terlebih dahulu peruntukan memori yang boleh dibebaskan dengan selamat, dan seterusnya mengeluarkan arahan mesin untuk mengendalikan proses pembersihan ini dengan cekap.

Pengkompil menjalankan analisis kod statik untuk menentukan sama ada nilai harus diperuntukkan pada bingkai tindanan fungsi yang membinanya atau sama ada ia mesti "melarikan diri" ke timbunan. Adalah penting untuk ambil perhatian bahawa bahasa Go tidak menyediakan sebarang kata kunci atau fungsi khusus yang membenarkan pembangun mengarahkan tingkah laku ini secara eksplisit. Sebaliknya, konvensyen dan corak dalam cara kod ditulis yang mempengaruhi proses membuat keputusan ini.

Nilai boleh melarikan diri ke dalam timbunan atas beberapa sebab. Jika pengkompil tidak dapat menentukan saiz pembolehubah, jika pembolehubah terlalu besar untuk dimuatkan pada timbunan, atau jika pengkompil tidak boleh dengan pasti memberitahu sama ada pembolehubah akan digunakan selepas fungsi tamat, nilai itu mungkin akan diperuntukkan pada timbunan. Selain itu, jika bingkai tindanan fungsi menjadi lapuk, ini juga boleh mencetuskan nilai untuk melarikan diri ke dalam timbunan.

Tetapi, bolehkah kita akhirnya menentukan sama ada nilai disimpan pada timbunan atau timbunan? Realitinya ialah hanya pengkompil yang mempunyai pengetahuan lengkap tentang tempat nilai akhirnya disimpan pada bila-bila masa.

Apabila nilai dikongsi di luar skop segera bingkai tindanan fungsi, nilai itu akan diperuntukkan pada timbunan. Di sinilah algoritma analisis melarikan diri memainkan peranan, mengenal pasti senario ini untuk memastikan program mengekalkan integritinya. Integriti ini penting untuk mengekalkan akses yang tepat, konsisten dan cekap kepada sebarang nilai dalam program. Oleh itu, analisis melarikan diri merupakan aspek asas pendekatan bahasa Go untuk pengurusan memori, mengoptimumkan prestasi dan keselamatan kod yang dilaksanakan.

Lihat contoh ini untuk memahami mekanisme asas di sebalik analisis melarikan diri:

<code class="language-go">var a int = 10
var p *int = &a</code>
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Arahan

//go:noinline menghalang fungsi ini daripada diselaraskan, memastikan contoh kami menunjukkan panggilan yang jelas untuk tujuan ilustrasi analisis melarikan diri.

Kami mentakrifkan dua fungsi, createStudent1 dan createStudent2, untuk menunjukkan hasil analisis pelarian yang berbeza. Kedua-dua versi cuba mencipta kejadian pengguna, tetapi ia berbeza dalam jenis pemulangannya dan cara ia mengendalikan memori.

  1. createStudent1: nilai semantik

Dalam createStudent1, cipta instance pelajar dan pulangkan mengikut nilai. Ini bermakna apabila fungsi kembali, salinan st dibuat dan diluluskan timbunan panggilan. Pengkompil Go menentukan bahawa &st tidak melarikan diri ke timbunan dalam kes ini. Nilai ini wujud pada bingkai tindanan createStudent1 dan salinan dibuat untuk bingkai tindanan utama.

Mastering Pointers in Go: Enhancing Safety, Performance, and Code Maintainability

Rajah 1 – Nilai Semantik 2. createStudent2: semantik penunjuk

Sebaliknya, createStudent2 mengembalikan penunjuk kepada tika pelajar, direka untuk berkongsi nilai pelajar merentas bingkai tindanan. Keadaan ini menekankan peranan kritikal analisis melarikan diri. Jika tidak diurus dengan betul, penunjuk yang dikongsi berisiko mengakses memori tidak sah.

Jika situasi yang diterangkan dalam Rajah 2 benar-benar berlaku, ia akan menimbulkan isu integriti yang ketara. Penunjuk akan menunjuk ke memori dalam timbunan panggilan tamat tempoh. Panggilan fungsi seterusnya ke utama akan menyebabkan memori yang ditunjuk sebelum ini diperuntukkan semula dan dimulakan semula.

Mastering Pointers in Go: Enhancing Safety, Performance, and Code Maintainability
Rajah 2 – Semantik penunjuk

Di sini, langkah analisis melarikan diri untuk mengekalkan integriti sistem. Memandangkan keadaan ini, pengkompil menentukan bahawa adalah tidak selamat untuk memperuntukkan nilai pelajar dalam bingkai tindanan createStudent2. Oleh itu, ia memilih untuk memperuntukkan nilai ini pada timbunan sebaliknya, yang merupakan keputusan yang dibuat pada masa pembinaan.

Sesuatu fungsi boleh terus mengakses memori dalam bingkainya sendiri melalui penuding bingkai. Walau bagaimanapun, mengakses memori di luar bingkainya memerlukan pengalihan melalui penunjuk. Ini bermakna bahawa nilai yang ditakdirkan untuk melarikan diri ke timbunan juga akan diakses secara tidak langsung.

Dalam bahasa Go, proses membina nilai tidak semestinya menunjukkan lokasi nilai dalam ingatan. Hanya apabila melaksanakan pernyataan pulangan, ia menjadi jelas bahawa nilai mesti melarikan diri ke timbunan.

Oleh itu, selepas pelaksanaan fungsi sedemikian, timbunan boleh dikonseptualisasikan dengan cara yang mencerminkan dinamik ini.

Selepas panggilan fungsi, timbunan boleh digambarkan seperti yang ditunjukkan di bawah.

Pembolehubah st pada bingkai tindanan createStudent2 mewakili nilai yang terletak pada timbunan dan bukannya tindanan. Ini bermakna mengakses nilai menggunakan st memerlukan akses penuding, bukannya akses langsung seperti yang dicadangkan oleh sintaks.

Untuk memahami keputusan pengkompil berkenaan peruntukan memori, anda boleh meminta laporan terperinci. Ini boleh dicapai dengan menggunakan suis -gcflags dengan pilihan -m dalam arahan go build.

<code class="language-go">var a int = 10
var p *int = &a</code>
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Pertimbangkan output arahan ini:

<code class="language-go">func main() {
    x := 42
    p := &x
    fmt.Printf("x: %v\n", x)
    fmt.Printf("&x: %v\n", &x)
    fmt.Printf("p: %v\n", p)
    fmt.Printf("*p: %v\n", *p)

    pp := &p
    fmt.Printf("**pp: %v\n", **pp)
}</code>
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Output ini menunjukkan hasil analisis pelarian pengkompil. Berikut ialah pecahan:

  • Pengkompil melaporkan bahawa ia tidak boleh menyelaraskan beberapa fungsi (createUser1, createUser2 dan utama) disebabkan arahan tertentu (go:noinline) atau kerana ia adalah fungsi bukan daun.
  • Untuk createUser1, output menunjukkan bahawa rujukan kepada st dalam fungsi tidak terlepas ke timbunan. Ini bermakna bahawa jangka hayat objek adalah terhad kepada bingkai tindanan fungsi. Sebaliknya, semasa createUser2, ia menyatakan bahawa &st melarikan diri ke timbunan. Ini jelas berkaitan dengan pernyataan pulangan, yang menyebabkan pembolehubah u yang diperuntukkan di dalam fungsi dipindahkan ke memori timbunan. Ini perlu kerana fungsi mengembalikan rujukan kepada st, yang perlu wujud di luar skop fungsi.

Pengumpulan Sampah

Bahasa Go termasuk mekanisme pengumpulan sampah terbina dalam yang mengendalikan peruntukan dan pelepasan memori secara automatik, berbeza dengan bahasa seperti C/C yang memerlukan pengurusan memori manual. Walaupun pengumpulan sampah melegakan pembangun daripada kerumitan pengurusan memori, ia memperkenalkan kependaman sebagai pertukaran.

Ciri yang ketara dalam bahasa Go ialah menghantar penunjuk mungkin lebih perlahan daripada menghantar nilai secara langsung. Tingkah laku ini disebabkan oleh sifat Go sebagai bahasa yang dikumpul sampah. Setiap kali penuding dihantar ke fungsi, bahasa Go menjalankan analisis melarikan diri untuk menentukan sama ada pembolehubah harus berada pada timbunan atau timbunan. Proses ini menimbulkan overhed, dan pembolehubah yang diperuntukkan pada timbunan boleh memburukkan lagi kependaman semasa kitaran kutipan sampah. Sebaliknya, pembolehubah terhad kepada tindanan memintas pemungut sampah sepenuhnya, mendapat manfaat daripada operasi tolak/pop yang mudah dan cekap yang dikaitkan dengan pengurusan memori tindanan.

Pengurusan memori pada tindanan sememangnya lebih pantas kerana ia mempunyai corak akses mudah di mana peruntukan memori dan deallokasi dilakukan hanya dengan menambah atau mengurangkan penunjuk atau integer. Sebaliknya, pengurusan ingatan timbunan melibatkan simpan kira yang lebih kompleks untuk peruntukan dan deallocation.

Bila menggunakan penunjuk dalam Go

  1. Salin struktur besar
    Walaupun penunjuk mungkin kelihatan kurang berprestasi disebabkan oleh overhed kutipan sampah, ia mempunyai kelebihan dalam struktur besar. Dalam kes ini, kecekapan yang diperoleh daripada mengelakkan penyalinan set data yang besar mungkin melebihi overhed yang diperkenalkan oleh kutipan sampah.
  2. Kebolehubahan
    Untuk menukar pembolehubah yang dihantar ke fungsi, penunjuk mesti dihantar. Pendekatan nilai lulus lalai bermaksud bahawa sebarang pengubahsuaian dibuat pada salinan dan oleh itu tidak menjejaskan pembolehubah asal dalam fungsi panggilan.
  3. Konsistensi API
    Menggunakan penerima penuding secara konsisten di seluruh API memastikan ia konsisten, yang amat berguna jika sekurang-kurangnya satu kaedah memerlukan penerima penuding untuk mengubah struktur.

Mengapa saya lebih suka nilai?

Saya lebih suka menghantar nilai berbanding penunjuk, berdasarkan beberapa hujah utama:

  1. Jenis saiz tetap
    Kami menganggap di sini jenis seperti integer, nombor titik terapung, struktur kecil dan tatasusunan. Jenis ini mengekalkan jejak memori yang konsisten yang biasanya sama atau lebih kecil daripada saiz penunjuk pada banyak sistem. Menggunakan nilai untuk jenis data bersaiz tetap yang lebih kecil ini adalah cekap memori dan konsisten dengan amalan terbaik untuk meminimumkan overhed.

  2. Ketidakbolehubah
    Melepasi nilai memastikan bahawa fungsi penerima mendapat salinan bebas data. Ciri ini penting untuk mengelakkan kesan sampingan yang tidak diingini; sebarang pengubahsuaian yang dibuat dalam fungsi kekal setempat, mengekalkan data asal di luar skop fungsi. Oleh itu, mekanisme panggilan demi nilai bertindak sebagai penghalang pelindung, memastikan integriti data.

  3. Kelebihan prestasi melepasi nilai
    Walaupun terdapat kemungkinan kebimbangan, menghantar nilai selalunya pantas dalam banyak kes dan boleh mengatasi prestasi menggunakan penunjuk dalam banyak kes:

    • Kecekapan penyalinan data: Untuk data yang kecil, gelagat penyalinan mungkin lebih cekap daripada mengendalikan arahan penunjuk. Akses terus kepada data mengurangkan kependaman penyahrujukan memori tambahan yang biasanya berlaku apabila menggunakan penunjuk.
    • Mengurangkan beban pada pemungut sampah: Nilai lulus secara langsung mengurangkan beban pada pemungut sampah. Dengan lebih sedikit petunjuk untuk dijejaki, proses kutipan sampah menjadi lebih diperkemas, meningkatkan prestasi keseluruhan.
    • Lokasi memori: Data yang diluluskan mengikut nilai biasanya disimpan bersebelahan dalam ingatan. Susunan ini memberi manfaat kepada mekanisme caching pemproses, membolehkan akses lebih cepat kepada data disebabkan peningkatan kadar hit cache. Lokasi spatial capaian data langsung berasaskan nilai memudahkan peningkatan prestasi yang ketara, terutamanya dalam operasi intensif pengiraan.

Kesimpulan

Ringkasnya, penunjuk dalam bahasa Go menyediakan akses alamat memori terus, yang bukan sahaja meningkatkan kecekapan tetapi juga meningkatkan fleksibiliti corak pengaturcaraan, dengan itu memudahkan manipulasi dan pengoptimuman data. Tidak seperti aritmetik penunjuk dalam C, pendekatan Go terhadap penunjuk direka untuk meningkatkan keselamatan dan kebolehselenggaraan, yang amat disokong oleh sistem pengumpulan sampah terbina dalamnya. Walaupun pemahaman dan penggunaan petunjuk dan nilai dalam bahasa Go akan sangat mempengaruhi prestasi dan keselamatan aplikasi, reka bentuk bahasa Go secara asasnya membimbing pembangun untuk membuat pilihan yang bijak dan berkesan. Melalui mekanisme seperti analisis melarikan diri, bahasa Go memastikan pengurusan memori yang optimum, mengimbangi kuasa penunjuk dengan keselamatan dan kesederhanaan nilai semantik. Keseimbangan yang teliti ini membolehkan pembangun mencipta aplikasi Go yang mantap, cekap dan memahami dengan jelas masa dan cara untuk memanfaatkan petunjuk.

Atas ialah kandungan terperinci Menguasai Penunjuk dalam Go: Meningkatkan Keselamatan, Prestasi dan Kebolehselenggaraan Kod. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

sumber:php.cn
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan