Mengapa menggunakan "pegangan" buram yang membutuhkan casting di API publik daripada pointer pointer typesafe?

27

Saya mengevaluasi perpustakaan yang API publiknya saat ini terlihat seperti ini:

libengine.h

/* Handle, used for all APIs */
typedef size_t enh;


/* Create new engine instance; result returned in handle */
int en_open(int mode, enh *handle);

/* Start an engine */
int en_start(enh handle);

/* Add a new hook to the engine; hook handle returned in h2 */
int en_add_hook(enh handle, int hooknum, enh *h2);

Perhatikan bahwa itu enhadalah pegangan umum, digunakan sebagai pegangan untuk beberapa tipe data yang berbeda ( mesin dan kait ).

Secara internal, sebagian besar API ini tentu saja melemparkan "pegangan" ke struktur internal yang telah mereka malloc:

engine.c

struct engine
{
    // ... implementation details ...
};

int en_open(int mode, *enh handle)
{
    struct engine *en;

    en = malloc(sizeof(*en));
    if (!en)
        return -1;

    // ...initialization...

    *handle = (enh)en;
    return 0;
}

int en_start(enh handle)
{
    struct engine *en = (struct engine*)handle;

    return en->start(en);
}

Secara pribadi, saya benci menyembunyikan hal-hal di belakang typedef, terutama ketika itu mengganggu keamanan jenis. (Diberikan enh, bagaimana saya tahu apa yang sebenarnya dimaksud?)

Jadi saya mengirimkan permintaan tarik, menyarankan perubahan API berikut (setelah memodifikasi seluruh perpustakaan agar sesuai):

libengine.h

struct engine;           /* Forward declaration */
typedef size_t hook_h;    /* Still a handle, for other reasons */


/* Create new engine instance, result returned in en */
int en_open(int mode, struct engine **en);

/* Start an engine */
int en_start(struct engine *en);

/* Add a new hook to the engine; hook handle returned in hh */
int en_add_hook(struct engine *en, int hooknum, hook_h *hh);

Tentu saja, ini membuat implementasi API internal terlihat jauh lebih baik, menghilangkan gips, dan menjaga keamanan jenis ke / dari perspektif konsumen.

libengine.c

struct engine
{
    // ... implementation details ...
};

int en_open(int mode, struct engine **en)
{
    struct engine *_e;

    _e = malloc(sizeof(*_e));
    if (!_e)
        return -1;

    // ...initialization...

    *en = _e;
    return 0;
}

int en_start(struct engine *en)
{
    return en->start(en);
}

Saya lebih suka ini karena alasan berikut:

Namun, pemilik proyek menolak keras permintaan tarik (diparafrasekan):

Secara pribadi saya tidak suka ide mengekspos struct engine. Saya masih berpikir cara saat ini lebih bersih & lebih ramah.

Awalnya saya menggunakan tipe data lain untuk pegangan kait, tetapi kemudian memutuskan untuk beralih menggunakan enh, jadi semua jenis pegangan berbagi tipe data yang sama agar tetap sederhana. Jika ini membingungkan, kita tentu bisa menggunakan tipe data lain.

Mari kita lihat apa pendapat orang lain tentang PR ini.

Perpustakaan ini saat ini dalam tahap beta pribadi, jadi tidak ada banyak kode konsumen yang perlu dikhawatirkan (belum). Juga, saya sedikit mengaburkan nama.


Bagaimana cara menangani buram lebih baik daripada yang disebut, struct buram?

Catatan: Saya mengajukan pertanyaan ini di Code Review , di mana ditutup.

Jonathon Reinhart
sumber
1
Saya telah mengedit judul untuk sesuatu yang saya yakini dengan lebih jelas mengungkapkan inti dari pertanyaan Anda. Silakan kembali jika saya salah mengartikannya.
Ixrec
1
@Icrec Itu lebih baik, terima kasih. Setelah menulis seluruh pertanyaan, saya kehabisan kapasitas mental untuk menghasilkan judul yang bagus.
Jonathon Reinhart

Jawaban:

33

Mantra "sederhana adalah lebih baik" telah menjadi terlalu dogmatis. Sederhana tidak selalu lebih baik jika menyulitkan hal-hal lain. Majelis sederhana - setiap perintah jauh lebih sederhana daripada perintah bahasa tingkat tinggi - namun program Majelis lebih kompleks daripada bahasa tingkat lebih tinggi yang melakukan hal yang sama. Dalam kasus Anda, jenis gagang seragam enhmembuat jenis lebih sederhana dengan biaya membuat fungsi menjadi kompleks. Karena biasanya jenis proyek cenderung tumbuh dalam tingkat sub-linear dibandingkan dengan fungsinya, karena proyek semakin besar, Anda biasanya lebih suka jenis yang lebih kompleks jika dapat membuat fungsi lebih sederhana - jadi dalam hal ini pendekatan Anda tampaknya menjadi yang benar.

Penulis proyek khawatir bahwa pendekatan Anda " mengeksposstruct engine ". Saya akan menjelaskan kepada mereka bahwa itu tidak mengekspos struct itu sendiri - hanya fakta bahwa ada struct bernama engine. Pengguna perpustakaan sudah harus menyadari tipe itu - mereka perlu tahu, misalnya, bahwa argumen pertama en_add_hookadalah tipe itu, dan argumen pertama adalah tipe yang berbeda. Jadi sebenarnya membuat API lebih kompleks, karena alih-alih memiliki fungsi "tanda tangan" dokumen jenis ini perlu didokumentasikan di tempat lain, dan karena kompiler tidak dapat lagi memverifikasi jenis untuk programmer.

Satu hal yang harus diperhatikan - API baru Anda membuat kode pengguna sedikit lebih rumit, karena alih-alih menulis:

enh en;
en_open(ENGINE_MODE_1, &en);

Mereka sekarang membutuhkan sintaks yang lebih kompleks untuk menyatakan pegangan mereka:

struct engine* en;
en_open(ENGINE_MODE_1, &en);

Solusinya, bagaimanapun, cukup sederhana:

struct _engine;
typedef struct _engine* engine

dan sekarang Anda dapat langsung menulis:

engine en;
en_open(ENGINE_MODE_1, &en);
Idan Arye
sumber
Saya lupa menyebutkan bahwa perpustakaan mengklaim mengikuti Linux Coding Style , yang kebetulan juga saya ikuti. Di sana, Anda akan melihat bahwa struktur pengetikan hanya untuk menghindari penulisan structsecara tegas tidak disarankan.
Jonathon Reinhart
@ Jonathon Reinhart dia mengetik pointer untuk struct bukan struct itu sendiri.
ratchet freak
@ JonathonReinhart dan benar-benar membaca tautan itu, saya melihat bahwa untuk "objek yang benar-benar buram" diperbolehkan. (bab 5 aturan a)
ratchet freak
Ya, tetapi hanya dalam kasus yang sangat jarang. Jujur saya percaya itu ditambahkan untuk menghindari penulisan ulang semua kode mm untuk berurusan dengan typedef pte. Lihatlah kode kunci putaran. Ini sepenuhnya khusus untuk lengkungan (tidak ada data umum) tetapi mereka tidak pernah menggunakan typedef.
Jonathon Reinhart
8
Saya lebih suka typedef struct engine engine;dan menggunakan engine*: Satu nama kurang diperkenalkan, dan itu membuatnya jelas itu seperti pegangan FILE*.
Deduplicator
16

Tampaknya ada kebingungan di kedua sisi di sini:

  • menggunakan pendekatan pegangan tidak memerlukan menggunakan jenis pegangan tunggal untuk semua pegangan
  • mengekspos structnama tidak mengekspos detailnya (hanya keberadaannya)

Ada keuntungan menggunakan pegangan daripada bare pointer, dalam bahasa seperti C, karena menyerahkan pointer memungkinkan manipulasi langsung pointee (termasuk panggilan ke free) sedangkan menyerahkan pegangan mengharuskan klien pergi melalui API untuk melakukan tindakan apa pun .

Namun, pendekatan memiliki satu jenis pegangan, yang didefinisikan melalui a typedefbukan jenis aman, dan dapat menyebabkan banyak kesedihan.

Saran pribadi saya adalah pindah ke tipe pegangan yang aman, yang saya pikir akan memuaskan Anda berdua. Ini dilakukan agak sederhana:

typedef struct {
    size_t id;
} enh;

typedef struct {
    size_t id;
} oth;

Sekarang, seseorang tidak dapat secara tidak sengaja melewati 2sebagai pegangan atau seseorang dapat secara tidak sengaja melewati pegangan ke sapu dimana pegangan ke mesin diharapkan.


Jadi saya mengirimkan permintaan tarik, menyarankan perubahan API berikut (setelah memodifikasi seluruh perpustakaan agar sesuai)

Itu adalah kesalahan Anda: sebelum terlibat dalam pekerjaan yang signifikan di pustaka sumber terbuka, hubungi penulis / pengelola untuk membahas perubahan di muka . Ini akan memungkinkan Anda berdua untuk menyetujui apa yang harus dilakukan (atau tidak dilakukan), dan menghindari pekerjaan yang tidak perlu dan frustrasi yang dihasilkan dari itu.

Matthieu M.
sumber
1
Terima kasih. Anda tidak masuk ke apa yang harus dilakukan dengan pegangan. Saya telah mengimplementasikan API berbasis pegangan yang sebenarnya , di mana pointer tidak pernah diekspos, meskipun melalui typedef. Ini melibatkan ~ pencarian data yang mahal pada entri setiap panggilan API - seperti cara Linux mencari struct filedari int fd. Ini tentunya berlebihan untuk IMO pustaka mode pengguna.
Jonathon Reinhart
@ JonathonReinhart: Yah, karena perpustakaan sudah menyediakan pegangan, saya tidak merasa perlu untuk memperluas. Memang ada beberapa pendekatan, dari hanya mengubah pointer ke integer untuk memiliki "kumpulan" dan menggunakan ID sebagai kunci. Anda bahkan dapat mengubah pendekatan antara Debug (pencarian ID +, untuk validasi) dan Rilis (hanya pointer yang dikonversi, untuk kecepatan).
Matthieu M.
Penggunaan kembali indeks tabel integer akan benar-benar menderita masalah ABA , di mana objek (indeks 3) dibebaskan, kemudian objek baru dibuat dan sayangnya ditugaskan indeks 3lagi. Sederhananya, sulit untuk memiliki mekanisme seumur hidup objek aman di C kecuali penghitungan referensi (bersama dengan konvensi tentang kepemilikan bersama terhadap objek) dibuat menjadi bagian eksplisit dari desain API.
rwong
2
@ rwong: Ini hanya masalah skema naif; Anda dapat dengan mudah mengintegrasikan penghitung zaman, misalnya, sehingga ketika pegangan lama ditentukan, Anda akan mendapatkan ketidakcocokan zaman.
Matthieu M.
1
@JonathonReinhart saran: Anda dapat menyebutkan "aturan aliasing yang ketat" dalam pertanyaan Anda untuk membantu mengarahkan diskusi menuju aspek yang lebih penting.
rwong
3

Berikut adalah situasi di mana pegangan buram diperlukan;

struct SimpleEngine {
    int type;  // always SimpleEngine.type = 1
    int a;
};

struct ComplexEngine {
    int type;  // always ComplexEngine.type = 2
    int a, b, c;
};

int en_start(enh handle) {
    switch(*(int*)handle) {
    case 1:
        // treat handle as SimpleEngine
        return start_simple_engine(handle);
    case 2:
        // treat handle as ComplexEngine
        return start_complex_engine(handle);
    }
}

Ketika pustaka memiliki dua atau lebih tipe struct yang memiliki bagian header yang sama dari bidang, seperti "tipe" di atas, tipe-tipe struct ini dapat dianggap memiliki struct induk yang sama (seperti kelas dasar dalam C ++).

Anda dapat mendefinisikan bagian header sebagai "mesin struct", seperti ini;

struct engine {
    int type;
};

struct SimpleEngine {
    struct engine base;
    int a;
};

struct ComplexEngine {
    struct engine base;
    int a, b, c;
};

int en_start(struct engine *en) { ... }

Tapi itu adalah keputusan opsional karena tipe-gips diperlukan terlepas dari menggunakan engine struct.

Kesimpulan

Dalam beberapa kasus, ada alasan mengapa pegangan buram digunakan sebagai pengganti struct buram.

Akio Takahashi
sumber
Saya pikir menggunakan serikat pekerja membuat ini lebih aman daripada gips berbahaya ke bidang yang bisa dipindahkan. Lihat intisari ini yang saya kumpulkan menunjukkan contoh lengkap.
Jonathon Reinhart
Tapi sebenarnya, menghindari switchdi tempat pertama, dengan menggunakan "fungsi virtual" mungkin ideal, dan menyelesaikan seluruh masalah.
Jonathon Reinhart
Desain Anda di intisari lebih kompleks daripada yang saya sarankan. Tentunya, ini membuat casting lebih sedikit, tipe aman dan cerdas, tetapi memperkenalkan lebih banyak kode dan tipe. Menurut pendapat saya, sepertinya terlalu sulit untuk mendapatkan tipe-aman. Saya, dan mungkin penulis perpustakaan memutuskan untuk mengikuti KISS daripada jenis keamanan.
Akio Takahashi
Nah, jika Anda ingin membuatnya tetap sederhana, Anda juga bisa menghilangkan pengecekan kesalahan!
Jonathon Reinhart
Menurut pendapat saya, kesederhanaan desain lebih disukai daripada sejumlah pemeriksaan kesalahan. Dalam kasus ini, pemeriksaan kesalahan semacam itu hanya ada di fungsi API. Selain itu, Anda dapat menghapus gips dengan menggunakan penyatuan, tetapi ingat bahwa penyatuan secara alami tidak aman.
Akio Takahashi
2

Manfaat yang paling jelas dari pendekatan pegangan adalah bahwa Anda dapat memodifikasi struktur internal tanpa melanggar API eksternal. Memang, Anda masih harus memodifikasi perangkat lunak klien, tetapi setidaknya Anda tidak mengubah antarmuka.

Hal lain yang dilakukannya adalah menyediakan kemampuan untuk memilih dari berbagai jenis yang berbeda saat runtime, tanpa harus menyediakan antarmuka API eksplisit untuk masing-masing. Beberapa aplikasi, seperti pembacaan sensor dari beberapa jenis sensor yang berbeda di mana masing-masing sensor sedikit berbeda dan menghasilkan data yang sedikit berbeda, merespons dengan baik terhadap pendekatan ini.

Karena bagaimanapun Anda akan menyediakan struktur untuk klien Anda, Anda mengorbankan sedikit keamanan jenis (yang masih dapat diperiksa saat runtime) untuk API yang jauh lebih sederhana, meskipun membutuhkan pengecoran.

Robert Harvey
sumber
5
"Anda dapat memodifikasi struktur internal tanpa .." - Anda juga dapat dengan pendekatan deklarasi maju.
user253751
Bukankah pendekatan "deklarasi maju" masih mengharuskan Anda untuk mendeklarasikan jenis tanda tangan? Dan bukankah tanda tangan jenis itu masih berubah jika Anda mengubah struct?
Robert Harvey
Penerusan deklarasi hanya mengharuskan Anda untuk mendeklarasikan nama tipe - strukturnya tetap tersembunyi.
Idan Arye
Lalu apa manfaat dari deklarasi maju jika bahkan tidak menegakkan struktur tipe?
Robert Harvey
6
@RobertHarvey Ingat - ini C yang sedang kita bicarakan. Tidak ada metode, jadi selain nama dan struktur tidak ada yang lain untuk jenisnya. Jika memang menegakkan struktur itu akan identik dengan deklarasi biasa. Inti dari mengekspos nama tanpa menegakkan struktur adalah bahwa Anda dapat menggunakan tipe fungsi tanda tangan itu. Tentu saja, tanpa struktur Anda hanya dapat menggunakan pointer ke tipe, karena kompiler tidak dapat mengetahui ukurannya, tetapi karena tidak ada penunjuk pointer implisit dalam C menggunakan pointer cukup baik untuk mengetik statis untuk melindungi Anda.
Idan Arye
2

Déjà vu

Bagaimana cara menangani buram lebih baik daripada yang disebut, struct buram?

Saya menemui skenario yang sama persis , hanya dengan beberapa perbedaan yang halus. Kami sudah, di SDK kami, banyak hal seperti ini:

typedef void* SomeHandle;

Usul belaka saya adalah membuatnya cocok dengan tipe internal kami:

typedef struct SomeVertex* SomeHandle;

Bagi pihak ketiga yang menggunakan SDK, seharusnya tidak ada bedanya sama sekali. Ini adalah jenis buram. Siapa peduli? Itu tidak berpengaruh pada ABI * atau kompatibilitas sumber, dan menggunakan rilis baru SDK memerlukan plugin yang harus dikompilasi ulang.

* Perhatikan bahwa, seperti yang ditunjukkan oleh gnasher, sebenarnya bisa ada kasus di mana ukuran sesuatu seperti pointer ke struct dan void * sebenarnya mungkin ukuran yang berbeda dalam hal ini akan mempengaruhi ABI. Seperti dia, saya belum pernah menemukannya dalam latihan. Tapi dari sudut pandang itu, yang kedua sebenarnya bisa meningkatkan portabilitas dalam beberapa konteks yang tidak jelas, jadi itu alasan lain untuk memilih yang kedua, meskipun mungkin bisa diperdebatkan bagi kebanyakan orang.

Bug Pihak Ketiga

Selain itu, saya bahkan memiliki lebih banyak penyebab daripada keamanan jenis untuk pengembangan / debugging internal. Kami sudah memiliki sejumlah pengembang plugin yang memiliki bug dalam kode mereka karena dua pegangan yang sama ( Paneldan PanelNew, yaitu) keduanya menggunakan void*typedef untuk pegangan mereka, dan mereka secara tidak sengaja melewati gagang yang salah ke tempat yang salah karena hanya menggunakan void*untuk semuanya. Jadi itu sebenarnya menyebabkan bug di sisi mereka yang menggunakanSDK. Bug mereka juga menghabiskan waktu tim pengembangan internal, karena mereka akan mengirim laporan bug yang mengeluhkan bug di SDK kami, dan kami harus men-debug plugin dan menemukan bahwa itu sebenarnya disebabkan oleh bug di plugin yang melewati pegangan yang salah ke tempat yang salah (yang mudah diizinkan tanpa peringatan ketika setiap pegangan adalah alias untuk void*atau size_t). Jadi kami tidak perlu membuang waktu untuk menyediakan layanan debug untuk pihak ketiga karena kesalahan yang disebabkan oleh keinginan kami untuk kemurnian konseptual dalam menyembunyikan semua informasi internal, bahkan hanya nama - nama internal kami structs.

Menjaga Typedef

Perbedaannya adalah bahwa saya mengusulkan agar kita tetap berpegang pada typedefdiam, bukan untuk memiliki klien menulis struct SomeVertexyang akan mempengaruhi kompatibilitas sumber untuk rilis plugin masa depan. Sementara saya pribadi menyukai gagasan tidak mengetik structdalam C, dari perspektif SDK, typedefdapat membantu karena seluruh titik opacity. Jadi saya sarankan untuk merelaksasi standar ini hanya untuk API yang terbuka untuk umum. Untuk klien yang menggunakan SDK, seharusnya tidak masalah apakah pegangan adalah pointer ke struct, integer, dll. Satu-satunya hal yang penting bagi mereka adalah bahwa dua pegangan berbeda tidak alias tipe data yang sama sehingga mereka tidak salah memasukkan pegangan yang salah ke tempat yang salah.

Ketikkan Informasi

Yang paling penting untuk menghindari casting adalah untuk Anda, para pengembang internal. Jenis estetika menyembunyikan semua nama internal dari SDK adalah beberapa estetika konseptual yang datang dengan biaya yang signifikan dari kehilangan semua jenis informasi, dan mengharuskan kita untuk menaburkan gips ke dalam pengadu kita untuk mendapatkan informasi penting. Sementara seorang programmer C sebagian besar harus terbiasa dengan ini dalam C, membutuhkan ini sia-sia hanya meminta masalah.

Cita-cita Konseptual

Secara umum, Anda ingin berhati-hati terhadap tipe-tipe pengembang yang menaruh gagasan konseptual tentang kemurnian yang jauh di atas semua kebutuhan praktis dan harian. Itu akan mendorong pemeliharaan basis kode Anda ke tanah dalam mencari cita-cita utopis, membuat seluruh tim menghindari lotion berjemur di padang pasir karena takut itu tidak wajar dan dapat menyebabkan kekurangan vitamin D sementara setengah kru sekarat karena kanker kulit.

Preferensi Pengguna-Akhir

Bahkan dari sudut pandang pengguna-akhir yang ketat dari mereka yang menggunakan API, apakah mereka lebih suka kereta buggy atau API yang berfungsi dengan baik tetapi memperlihatkan beberapa nama yang mereka tidak bisa pedulikan sebagai gantinya? Karena itulah pertukaran praktis. Kehilangan informasi jenis yang tidak perlu di luar konteks umum meningkatkan risiko bug, dan dari basis kode skala besar dalam pengaturan tim selama beberapa tahun, hukum Murphy cenderung cukup berlaku. Jika Anda secara berlebihan meningkatkan risiko bug, kemungkinan Anda setidaknya akan mendapatkan lebih banyak bug. Tidak perlu waktu terlalu lama dalam pengaturan tim besar untuk menemukan bahwa setiap jenis kesalahan manusia yang dibayangkan pada akhirnya akan berubah dari potensi menjadi kenyataan.

Jadi mungkin itu pertanyaan yang diajukan kepada pengguna. "Apakah Anda lebih suka SDK buggier atau yang memperlihatkan beberapa nama buram internal yang Anda bahkan tidak akan pernah peduli?" Dan jika pertanyaan itu tampaknya dikotomi palsu, saya akan mengatakan lebih banyak pengalaman di tim dalam skala yang sangat besar diperlukan untuk menghargai fakta bahwa risiko yang lebih tinggi untuk bug pada akhirnya akan memanifestasikan bug nyata dalam jangka panjang. Tidak masalah seberapa besar pengembang percaya diri dalam menghindari bug. Dalam pengaturan tim-lebar, itu membantu lebih banyak untuk berpikir tentang link terlemah, dan setidaknya cara termudah dan tercepat untuk mencegah mereka dari tersandung.

Usul

Jadi saya sarankan kompromi di sini yang masih akan memberi Anda kemampuan untuk mempertahankan semua manfaat debug:

typedef struct engine* enh;

... bahkan dengan biaya mengetik struct, akankah itu benar-benar membunuh kita? Mungkin tidak, jadi saya merekomendasikan beberapa pragmatisme di pihak Anda juga, tetapi lebih kepada pengembang yang lebih suka untuk membuat debugging lebih sulit secara eksponensial dengan menggunakan di size_tsini dan melakukan casting ke / dari integer tanpa alasan yang baik kecuali untuk menyembunyikan informasi yang sudah ada. % disembunyikan bagi pengguna dan tidak mungkin membahayakan lebih dari size_t.


sumber
1
Ini perbedaan kecil : Menurut Standar C, semua "pointer ke struct" memiliki representasi yang identik, jadi lakukan semua "pointer to union", demikian juga "void *" dan "char *", tetapi void * dan "pointer" ke struct "dapat memiliki sizeof () dan / atau representasi yang berbeda. Dalam praktiknya, saya belum pernah melihat ini.
gnasher729
@ gnasher729 Sama, mungkin saya harus memenuhi syarat bagian sehubungan dengan potensi hilangnya portabilitas dalam casting ke void*atau size_tdan kembali sebagai alasan lain untuk menghindari casting berlebihan. Saya agak menghilangkannya karena saya juga tidak pernah melihatnya dalam praktik mengingat platform yang kami targetkan (yang selalu merupakan platform desktop: linux, OSX, Windows).
1
Kami berakhir dengantypedef struct uc_struct uc_engine;
Jonathon Reinhart
1

Saya menduga alasan sebenarnya adalah kelembaman, itu yang selalu mereka lakukan dan itu berhasil jadi mengapa mengubahnya?

Alasan utama yang bisa saya lihat adalah bahwa pegangan yang buram memungkinkan desainer meletakkan apa pun di belakangnya, bukan hanya struct. Jika API kembali dan menerima beberapa jenis buram mereka semua terlihat sama dengan penelepon dan tidak pernah ada masalah kompilasi atau kompilasi yang diperlukan jika perubahan cetak halus. Jika en_NewFlidgetTwiddler (handle ** newTwiddler) berubah untuk mengembalikan pointer ke Twiddler alih-alih handle, API tidak berubah dan kode baru apa pun akan diam-diam menggunakan pointer di mana sebelum menggunakan handle. Juga, tidak ada bahaya dari OS atau hal lain diam-diam "memperbaiki" pointer jika melewati melewati batas.

Kerugian dari itu, tentu saja, adalah bahwa penelepon dapat memberi makan apa saja ke dalamnya. Anda memiliki hal 64 bit? Dorong ke dalam slot 64 bit di panggilan API dan lihat apa yang terjadi.

en_TwiddleFlidget(engine, twiddler, flidget)
en_TwiddleFlidget(engine, flidget, twiddler)

Keduanya mengkompilasi tetapi saya yakin hanya satu dari mereka melakukan apa yang Anda inginkan.

MOo
sumber
1

Saya percaya bahwa sikap tersebut berasal dari filosofi lama untuk mempertahankan C library API dari penyalahgunaan oleh pemula.

Khususnya,

  • Penulis perpustakaan tahu itu adalah penunjuk ke struct, dan detail struct terlihat oleh kode perpustakaan.
  • Semua programmer berpengalaman yang menggunakan perpustakaan juga tahu itu adalah penunjuk ke beberapa struct buram;
    • Mereka memiliki pengalaman menyakitkan yang cukup sulit untuk diketahui untuk tidak mengacaukan byte yang disimpan dalam struct tersebut.
  • Pemrogram berpengalaman tidak tahu.
    • Mereka akan mencoba untuk memcpymenambah data buram atau byte atau kata-kata di dalam struct. Pergi meretas.

Penanggulangan tradisional lama adalah untuk:

  • Masquerade fakta bahwa pegangan buram sebenarnya adalah penunjuk ke struct buram yang ada di ruang proses-memori yang sama.
    • Untuk melakukannya dengan mengklaim itu adalah nilai integer yang memiliki jumlah bit yang sama dengan a void*
    • Untuk menjadi ekstra hati-hati, menyamarkan bit pointer juga, misalnya
      struct engine* peng = (struct engine*)((size_t)enh ^ enh_magic_number);

Ini hanya untuk mengatakan ia memiliki tradisi panjang; Saya tidak punya pendapat pribadi apakah itu benar atau salah.

rwong
sumber
3
Kecuali untuk xor konyol, solusi saya memberikan keamanan itu juga. Klien tetap tidak menyadari ukuran atau isi struktur, dengan manfaat tambahan dari keamanan tipe. Saya tidak melihat bagaimana menyalahgunakan size_t untuk memegang pointer lebih baik.
Jonathon Reinhart
@ JonathonReinhart sangat tidak mungkin bahwa klien akan benar-benar tidak menyadari struktur. Pertanyaannya lebih lanjut: bisakah mereka mendapatkan struktur dan bisakah mereka mengembalikan versi yang dimodifikasi ke perpustakaan Anda. Tidak hanya dengan open source, tetapi lebih umum. Solusinya adalah partisi memori modern, bukan XOR konyol.
Móż
Apa yang kamu bicarakan? Yang saya katakan adalah bahwa Anda tidak dapat mengkompilasi kode apa pun yang mencoba untuk mengubah referensi pointer ke struktur kata, atau melakukan apa pun yang membutuhkan pengetahuan tentang ukurannya. Tentu, Anda bisa memset (, 0,) di seluruh tumpukan proses, jika Anda benar-benar ingin.
Jonathon Reinhart
6
Argumen ini terdengar seperti menjaga terhadap Machiavelli . Jika pengguna ingin meneruskan sampah ke API saya, tidak mungkin saya bisa menghentikannya. Memperkenalkan antarmuka jenis-tidak aman seperti ini sangat membantu karena hal itu justru membuat penyalahgunaan API lebih mudah.
ComicSansMS
@ComicSansMS terima kasih untuk menyebutkan "kebetulan", karena itulah apa yang saya benar-benar mencoba untuk mencegah sini.
Jonathon Reinhart