Saya menulis kode C untuk sistem di mana alamat 0x0000 valid dan berisi port I / O. Oleh karena itu, setiap kemungkinan bug yang mengakses pointer NULL akan tetap tidak terdeteksi dan pada saat yang sama menyebabkan perilaku berbahaya.
Untuk alasan ini saya ingin mendefinisikan kembali NULL menjadi alamat lain, misalnya alamat yang tidak valid. Jika saya tidak sengaja mengakses alamat seperti itu, saya akan mendapatkan interupsi perangkat keras di mana saya dapat menangani kesalahan tersebut. Saya kebetulan memiliki akses ke stddef.h untuk kompiler ini, jadi saya benar-benar dapat mengubah header standar dan mendefinisikan ulang NULL.
Pertanyaan saya adalah: apakah ini akan bertentangan dengan standar C? Sejauh yang saya tahu dari 7.17 dalam standar, makro adalah implementasi-didefinisikan. Apakah ada hal lain dalam standar yang menyatakan bahwa NULL harus 0?
Masalah lainnya adalah banyak kompiler melakukan inisialisasi statis dengan mengatur semuanya ke nol, apa pun tipe datanya. Meskipun standar mengatakan bahwa kompilator harus mengatur bilangan bulat ke nol dan penunjuk ke NULL. Jika saya akan mendefinisikan ulang NULL untuk kompiler saya, maka saya tahu bahwa inisialisasi statis seperti itu akan gagal. Dapatkah saya menganggapnya sebagai perilaku kompilator yang salah meskipun saya dengan berani mengubah header kompiler secara manual? Karena saya tahu pasti bahwa kompilator khusus ini tidak mengakses makro NULL ketika melakukan inisialisasi statis.
mprotect
untuk mengamankan. Atau, jika platform tidak memiliki ASLR atau sejenisnya, alamat di luar memori fisik platform. Semoga berhasil.if(ptr) { /* do something on ptr*/ }
? Apakah akan berhasil jika NULL didefinisikan berbeda dari 0x0?Jawaban:
Standar C tidak memerlukan penunjuk nol untuk berada di alamat mesin nol. NAMUN, mentransmisikan
0
konstanta ke nilai pointer harus menghasilkanNULL
pointer (§6.3.2.3 / 3), dan mengevaluasi pointer nol sebagai boolean harus salah. Ini bisa sedikit canggung jika Anda benar - benar menginginkan alamat nol, danNULL
bukan alamat nol.Namun demikian, dengan modifikasi (berat) pada kompilator dan pustaka standar, bukan tidak mungkin untuk
NULL
direpresentasikan dengan pola bit alternatif sambil tetap benar-benar sesuai dengan pustaka standar. Namun, tidak cukup hanya mengubah definisiNULL
itu sendiri, karena kemudianNULL
mengevaluasi menjadi benar.Secara khusus, Anda perlu:
-1
.0
untuk memeriksa nilai ajaib sebagai gantinya (§6.5.9 / 6)Ada beberapa hal yang tidak harus Anda tangani. Sebagai contoh:
Setelah ini,
p
TIDAK dijamin akan menjadi pointer nol. Hanya tugas konstan yang perlu ditangani (ini adalah pendekatan yang baik untuk mengakses alamat benar nol). Juga:Juga:
x
tidak dijamin0
.Singkatnya, kondisi ini rupanya dipertimbangkan oleh komite bahasa C, dan pertimbangan dibuat bagi mereka yang akan memilih representasi alternatif untuk NULL. Yang harus Anda lakukan sekarang adalah membuat perubahan besar pada kompiler Anda, dan hai, presto Anda sudah selesai :)
Sebagai catatan tambahan, dimungkinkan untuk mengimplementasikan perubahan ini dengan tahap transformasi kode sumber sebelum kompiler tepat. Artinya, alih-alih aliran normal preprocessor -> compiler -> assembler -> linker, Anda akan menambahkan preprocessor -> transformasi NULL -> compiler -> assembler -> linker. Kemudian Anda bisa melakukan transformasi seperti:
Ini akan membutuhkan parser C lengkap, serta parser tipe dan analisis typedefs dan deklarasi variabel untuk menentukan pengidentifikasi mana yang sesuai dengan pointer. Namun, dengan melakukan ini, Anda dapat menghindari keharusan untuk membuat perubahan pada bagian pembuatan kode dari kompilator yang sesuai. dentang mungkin berguna untuk mengimplementasikan ini - saya mengerti itu dirancang dengan transformasi seperti ini dalam pikiran. Anda mungkin masih perlu melakukan perubahan pada pustaka standar juga tentunya.
sumber
NULL
.Standar menyatakan bahwa ekspresi konstanta integer dengan nilai 0, atau ekspresi semacam itu dikonversi ke
void *
tipe, adalah konstanta penunjuk nol. Ini berarti bahwa(void *)0
pointer selalu null, tetapi diberikanint i = 0;
,(void *)i
tidak perlu.Implementasi C terdiri dari kompilator bersama dengan headernya. Jika Anda mengubah tajuk untuk didefinisikan ulang
NULL
, tetapi tidak mengubah kompiler untuk memperbaiki inisialisasi statis, Anda telah membuat implementasi yang tidak sesuai. Ini adalah keseluruhan implementasi yang disatukan yang memiliki perilaku yang salah, dan jika Anda melanggarnya, Anda benar-benar tidak punya orang lain untuk disalahkan;)Anda harus memperbaiki lebih dari sekedar inisialisasi statis, tentu saja - diberi pointer
p
,if (p)
sama denganif (p != NULL)
, karena aturan di atas.sumber
Jika Anda menggunakan pustaka C std, Anda akan mengalami masalah dengan fungsi yang dapat mengembalikan NULL. Misalnya dokumentasi malloc menyatakan:
Karena malloc dan fungsi terkait telah dikompilasi ke dalam biner dengan nilai NULL tertentu, jika Anda mendefinisikan ulang NULL, Anda tidak akan dapat langsung menggunakan pustaka C std kecuali Anda dapat membangun kembali seluruh rantai alat Anda, termasuk C std libs.
Juga karena penggunaan NULL di perpustakaan std, jika Anda mendefinisikan ulang NULL sebelum menyertakan header std, Anda mungkin menimpa definisi NULL yang terdaftar di header. Apa pun yang sebaris akan menjadi tidak konsisten dari objek yang dikompilasi.
Saya malah akan mendefinisikan NULL Anda sendiri, "MYPRODUCT_NULL", untuk penggunaan Anda sendiri dan baik menghindari atau menerjemahkan dari / ke perpustakaan C std.
sumber
Biarkan NULL sendiri dan perlakukan IO ke port 0x0000 sebagai kasus khusus, mungkin menggunakan rutin yang ditulis dalam assembler, dan dengan demikian tidak tunduk pada semantik C standar. IOW, jangan tentukan ulang NULL, tentukan ulang port 0x00000.
Perhatikan bahwa jika Anda menulis atau memodifikasi kompiler C, pekerjaan yang diperlukan untuk menghindari dereferensi NULL (dengan asumsi bahwa dalam kasus Anda CPU tidak membantu) adalah sama tidak peduli bagaimana NULL didefinisikan, jadi lebih mudah untuk membiarkan NULL didefinisikan sebagai nol, dan pastikan bahwa nol tidak pernah dapat dideferensiasi dari C.
sumber
*p
,p[]
ataup()
, jadi kompilator hanya perlu memperhatikan yang melindungi IO port 0x0000.Mempertimbangkan kesulitan ekstrim dalam mendefinisikan ulang NULL seperti yang disebutkan oleh orang lain, mungkin lebih mudah untuk mendefinisikan ulang dereferensi untuk alamat perangkat keras terkenal. Saat membuat alamat, tambahkan 1 ke setiap alamat terkenal, sehingga porta IO Anda yang terkenal adalah:
Jika alamat yang Anda tuju dikelompokkan bersama dan Anda dapat merasa aman bahwa menambahkan 1 ke alamat tidak akan menimbulkan konflik dengan apa pun (yang seharusnya tidak dalam banyak kasus), Anda mungkin dapat melakukannya dengan aman. Dan kemudian Anda tidak perlu khawatir tentang membangun kembali rantai alat / std lib dan ekspresi Anda dalam bentuk:
masih bekerja
Gila saya tahu, tapi saya hanya berpikir saya akan membuang ide itu :).
sumber
Pola bit untuk penunjuk nol mungkin tidak sama dengan pola bit untuk bilangan bulat 0. Tetapi perluasan makro NULL harus berupa konstanta penunjuk nol, yaitu bilangan bulat konstan dengan nilai 0 yang dapat dicor ke (void *).
Untuk mencapai hasil yang Anda inginkan sambil tetap menyesuaikan diri, Anda harus memodifikasi (atau mungkin mengonfigurasi) rantai perkakas Anda, tetapi hal itu dapat dicapai.
sumber
Anda sedang mencari masalah. Mendefinisikan ulang
NULL
ke nilai bukan nol akan merusak kode ini:sumber