Dalam C, mengapa beberapa orang melemparkan pointer sebelum membebaskannya?

167

Saya sedang bekerja pada basis kode lama dan hampir setiap doa gratis () menggunakan gips pada argumennya. Sebagai contoh,

free((float *)velocity);
free((float *)acceleration);
free((char *)label);

di mana setiap pointer adalah tipe yang sesuai (dan cocok). Saya tidak melihat gunanya melakukan ini sama sekali. Itu kode yang sangat lama, jadi saya bertanya-tanya apakah ini masalah K&R. Jika demikian, saya sebenarnya ingin mendukung kompiler lama yang mungkin membutuhkan ini, jadi saya tidak ingin menghapusnya.

Apakah ada alasan teknis untuk menggunakan gips ini? Saya bahkan tidak melihat banyak alasan pragmatis untuk menggunakannya. Apa gunanya mengingatkan diri kita tentang tipe data sebelum membebaskannya?

EDIT: Pertanyaan ini bukan duplikat dari pertanyaan lain. Pertanyaan lain adalah kasus khusus dari pertanyaan ini, yang saya pikir jelas jika pemilih dekat akan membaca semua jawaban.

Colophon: Saya memberi "const answer" tanda centang karena itu adalah alasan nyata mengapa hal ini perlu dilakukan; Namun, jawaban tentang hal itu menjadi kebiasaan pra-ANSI C (setidaknya di antara beberapa programmer) tampaknya menjadi alasan itu digunakan dalam kasus saya. Banyak poin bagus oleh banyak orang di sini. Terima kasih atas kontribusi kamu.

Person Person II
sumber
13
"Apa gunanya mengingatkan diri kita tentang tipe data sebelum membebaskannya?" Mungkin tahu berapa banyak memori yang akan dibebaskan?
m0skit0
12
@ Codor Compiler tidak melakukan deallokasi, sistem operasi tidak.
m0skit0
20
@ m0skit0 "Mungkin tahu berapa banyak memori yang akan dibebaskan?" Ketik tidak perlu tahu berapa banyak yang harus gratis. Cast karena alasan itu hanya coding yang buruk.
user694733
9
@ m0skit0 Casting demi keterbacaan selalu merupakan kode yang buruk, karena casting mengubah cara tipe ditafsirkan dan mungkin menyembunyikan kesalahan serius. Ketika keterbacaan dibutuhkan, komentar lebih baik.
user694733
66
Pada zaman kuno ketika dinosaurus berjalan di bumi, dan menulis buku-buku pemrograman, saya percaya tidak ada void*dalam standar C, tetapi hanya char*. Jadi jika temuan arkeologis Anda mengungkapkan kode yang melepaskan parameter ke (), saya percaya itu harus dari periode waktu itu, atau ditulis oleh makhluk dari waktu itu. Saya tidak dapat menemukan sumber untuk ini, jadi saya akan menahan diri untuk tidak menjawab.
Lundin

Jawaban:

171

Casting mungkin diperlukan untuk menyelesaikan peringatan kompiler jika petunjuknya adalah const. Berikut adalah contoh kode yang menyebabkan peringatan tanpa melemparkan argumen gratis:

const float* velocity = malloc(2*sizeof(float));
free(velocity);

Dan kompiler (gcc 4.8.3) mengatakan:

main.c: In function main’:
main.c:9:5: warning: passing argument 1 of free discards const qualifier from pointer target type [enabled by default]
     free(velocity);
     ^
In file included from main.c:2:0:
/usr/include/stdlib.h:482:13: note: expected void *’ but argument is of type const float *’
 extern void free (void *__ptr) __THROW;

Jika Anda menggunakan free((float*) velocity);kompiler berhenti mengeluh.

Manos Nikolaidis
sumber
2
@ m0skit0 itu tidak menjelaskan mengapa seseorang akan dilemparkan float*sebelum membebaskan. Saya mencoba free((void *)velocity);dengan gcc 4.8.3. Tentu saja itu tidak akan berfungsi dengan kompiler kuno
Manos Nikolaidis
54
Tetapi mengapa Anda perlu mengalokasikan memori konstan secara dinamis? Anda tidak akan pernah bisa menggunakannya!
Nils_M
33
@ Nils_M itu adalah contoh sederhana untuk membuat titik. Apa yang saya lakukan dalam kode aktual dalam suatu fungsi adalah mengalokasikan memori non-const, menetapkan nilai, melemparkan ke pointer const dan mengembalikannya. Sekarang, ada pointer ke memori const preassigned bahwa seseorang harus membebaskan.
Manos Nikolaidis
2
Contoh : “Subrutin-subrutin ini mengembalikan string dalam memori yang baru dipusatkan, ditunjukkan oleh * stringValueP, yang akhirnya harus Anda bebaskan. Kadang-kadang, fungsi OS yang Anda gunakan untuk membebaskan memori dinyatakan untuk mengambil pointer ke sesuatu yang tidak konstan sebagai argumennya, jadi karena * stringValueP adalah pointer ke const. ”
Carsten S
3
Keliru, jika fungsi mengambil const char *psebagai argumen dan kemudian membebaskan itu, hal yang benar untuk dilakukan adalah untuk tidak cor puntuk char*sebelum menelepon gratis. Ini untuk tidak mendeklarasikannya sebagai mengambil const char *ptempat, karena itu memodifikasi *p dan harus dinyatakan sesuai. (Dan jika membutuhkan pointer const bukan pointer ke const, int *const pAnda tidak perlu melakukan cast karena itu benar-benar legal dan dengan demikian berfungsi dengan baik tanpa cast.)
Ray
59

Pra-standar C tidak memiliki void*tetapi hanya char*, sehingga Anda harus membuang semua parameter yang dilewati. Jika Anda menemukan kode C kuno, karena itu Anda mungkin menemukan gips tersebut.

Pertanyaan serupa dengan referensi .

Ketika standar C pertama dirilis, prototipe untuk malloc dan bebas berubah dari keharusan char*ke void*yang mereka miliki saat ini.

Dan tentu saja dalam standar C, gips seperti itu berlebihan dan hanya membahayakan keterbacaan.

Lundin
sumber
23
Tetapi mengapa Anda melemparkan argumen ke freetipe yang sama seperti yang sudah ada?
jwodder
4
@ chux Masalah dengan pra-standar hanyalah: tidak ada kewajiban untuk apa pun. Orang-orang hanya menunjuk buku K&R untuk kanon karena itu satu-satunya yang mereka miliki. Dan seperti yang dapat kita lihat dari beberapa contoh dalam K&R edisi ke-2, K&R sendiri bingung tentang bagaimana para pelempar parameter freebekerja dalam standar C (Anda tidak perlu melakukan cast). Saya belum membaca edisi 1 jadi saya tidak tahu apakah mereka bingung di era pra-standar 80-an juga.
Lundin
7
Pra-standar C tidak memiliki void*, tetapi tidak memiliki prototipe fungsi juga, jadi melemparkan argumen freemasih tidak perlu bahkan di K&R (dengan asumsi semua tipe data pointer menggunakan representasi yang sama).
Ian Abbott
6
Untuk beberapa alasan yang dinyatakan dalam komentar, saya kira jawaban ini tidak masuk akal.
R .. GitHub BERHENTI MEMBANTU ICE
4
Saya tidak melihat bagaimana jawaban ini akan menjawab apa pun yang relevan. Pertanyaan aslinya melibatkan pemain untuk tipe lain, tidak hanya untuk char *. Apa artinya masuk kompiler lama tanpa void? Apa yang akan dicapai oleh para pemain seperti itu?
AnT
34

Berikut adalah contoh di mana gratis akan gagal tanpa gips:

volatile int* p = (volatile int*)malloc(5 * sizeof(int));
free(p);        // fail: warning C4090: 'function' : different 'volatile' qualifiers
free((int*)p);  // success :)
free((void*)p); // success :)

Dalam C Anda bisa mendapatkan peringatan (mendapatkannya di VS2012). Di C ++ Anda akan mendapatkan kesalahan.

Mengesampingkan kasus langka, casting hanya menggembungkan kode ...

Sunting: Saya memilih untuk void*tidak int*menunjukkan kegagalan. Ini akan bekerja sama seperti yang int*akan dikonversi void*secara implisit. int*Kode ditambahkan .

egur
sumber
Perhatikan bahwa dalam kode yang diposting dalam pertanyaan, gips bukan untuk void *, tetapi ke float *dan char *. Para pemain tidak hanya asing, mereka salah.
Andrew Henle
1
Pertanyaannya sebenarnya justru sebaliknya.
m0skit0
1
Saya tidak mengerti jawabannya; dalam arti apa akan free(p)gagal? Apakah akan memberikan kesalahan kompiler?
Codor
1
Ini adalah poin yang bagus. Sama halnya dengan constpointer kualifikasi, tentu saja.
Lundin
2
volatiletelah ada sejak C dibakukan jika tidak lagi. Itu tidak ditambahkan di C99.
R .. GitHub BERHENTI MEMBANTU ICE
30

Alasan lama: 1. Dengan menggunakan free((sometype*) ptr), kode eksplisit tentang jenis pointer harus dianggap sebagai bagian dari free()panggilan. Pemeran eksplisit berguna ketika free()diganti dengan (do-it-yourself) DIY_free().

#define free(ptr) DIY_free(ptr, sizeof (*ptr))

A DIY_free()adalah cara, terutama dalam mode debug, untuk melakukan analisis run-time dari pointer yang dibebaskan. Ini sering dipasangkan dengan a DIY_malloc()untuk menambahkan sentensial, jumlah penggunaan memori global, dll. Grup saya menggunakan teknik ini selama bertahun-tahun sebelum alat yang lebih modern muncul. Itu berkewajiban bahwa item yang dibebastugaskan dilemparkan ke jenis awalnya dialokasikan.

  1. Mengingat banyaknya waktu yang dihabiskan untuk melacak masalah memori, dll., Trik-trik kecil seperti casting tipe free'd akan membantu dalam mencari dan mempersempit debugging.

Modern: Menghindari constdan volatilememperingatkan seperti yang disampaikan oleh Manos Nikolaidis @ dan @egur . Pikir saya akan perhatikan efek dari 3 kualifikasi : const, volatile, dan restrict.

[sunting] Ditambahkan char * restrict *rp2per @R .. komentar

void free_test(const char *cp, volatile char *vp, char * restrict rp, 
    char * restrict *rp2) {
  free(cp);  // warning
  free(vp);  // warning
  free(rp);  // OK
  free(rp2);  // warning
}

int main(void) {
  free_test(0,0,0,0);
  return 0;
}
chux - Pasang kembali Monica
sumber
3
restricthanya masalah karena di mana ia ditempatkan - itu mempengaruhi objek rpbukan tipe-menunjuk-ke. Jika Anda malah melakukannya char *restrict *rp, maka itu penting.
R .. GitHub BERHENTI MEMBANTU ICE
16

Berikut adalah hipotesis alternatif lain.

Kami diberitahu bahwa program ini ditulis pra-C89, yang berarti tidak dapat mengatasi semacam ketidakcocokan dengan prototipe free, karena tidak hanya tidak ada hal seperti constatau void *sebelum C89, tidak ada yang namanya sebuah prototipe fungsi sebelum C89. stdlib.hsendiri adalah penemuan komite. Jika header sistem repot untuk mendeklarasikan freesama sekali, mereka akan melakukannya seperti ini:

extern free();  /* no `void` return type either! */

Sekarang, titik kunci di sini adalah bahwa tidak adanya prototipe fungsi berarti kompiler tidak memeriksa jenis argumen . Itu menerapkan promosi argumen default (yang sama yang masih berlaku untuk panggilan fungsi variadic) dan hanya itu. Tanggung jawab untuk membuat argumen di setiap callsite sesuai dengan harapan callee sepenuhnya berada di tangan programmer.

Namun, ini masih tidak berarti perlunya mengajukan argumen freepada kebanyakan penyusun K&R. Fungsi seperti

free_stuff(a, b, c)
    float *a;
    char *b;
    int *c;
{
    free(a);
    free(b);
    free(c);
}

seharusnya dikompilasi dengan benar. Jadi saya pikir apa yang kita punya di sini adalah program yang ditulis untuk mengatasi kompiler kereta untuk lingkungan yang tidak biasa: misalnya, lingkungan di mana sizeof(float *) > sizeof(int)dan kompiler tidak akan menggunakan konvensi pemanggilan yang sesuai untuk pointer kecuali jika Anda melemparkan mereka pada titik panggilan.

Saya tidak mengetahui adanya lingkungan seperti itu, tetapi itu tidak berarti tidak ada. Calon yang paling mungkin muncul dalam pikiran adalah kompiler "kecil C" yang dipotong untuk micros 8 dan 16 bit pada awal 1980-an. Saya juga tidak akan terkejut mengetahui bahwa Crays awal memiliki masalah seperti ini.

zwol
sumber
1
Paruh pertama saya sepenuhnya setuju. Dan babak ke-2 adalah dugaan yang menarik dan masuk akal.
chux - Reinstate Monica
9

gratis hanya mengambil pointer non-const sebagai parameter. Jadi dalam kasus pointer pointer, casting eksplisit ke pointer non-const diperlukan.

Tidak dapat membebaskan pointer const di C

Tak seorangpun
sumber