ใน C เหตุใดบางคนเหวี่ยงตัวชี้ก่อนที่จะปล่อย


167

ฉันกำลังทำงานกับฐานรหัสเก่าและทุก ๆ การเรียกใช้ฟรี () ใช้การส่งผ่านการโต้แย้ง ตัวอย่างเช่น,

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

โดยที่ตัวชี้แต่ละตัวเป็นประเภทที่ตรงกัน (และการจับคู่) ฉันไม่เห็นจุดที่จะทำสิ่งนี้เลย มันเป็นรหัสเก่ามากดังนั้นฉันจึงสงสัยว่ามันจะเป็นของ K&R หรือเปล่า ถ้าเป็นเช่นนั้นฉันต้องการสนับสนุนคอมไพเลอร์ตัวเก่าที่อาจจำเป็นต้องใช้ดังนั้นฉันจึงไม่ต้องการลบออก

มีเหตุผลทางเทคนิคในการใช้การปลดเปลื้องเหล่านี้หรือไม่ ฉันไม่เห็นเหตุผลที่ใช้ประโยชน์ได้มากนัก อะไรคือจุดที่ทำให้เรานึกถึงประเภทข้อมูลที่ถูกต้องก่อนที่จะทำให้เป็นอิสระ

แก้ไข: คำถามนี้ไม่ได้ซ้ำกับคำถามอื่น คำถามอื่นเป็นกรณีพิเศษของคำถามนี้ซึ่งฉันคิดว่าชัดเจนว่าผู้มีสิทธิเลือกตั้งอย่างใกล้ชิดจะอ่านคำตอบทั้งหมดหรือไม่

Colophon: ฉันจะให้เครื่องหมายถูก "ตอบ const" เพราะมันเป็นเหตุผลที่แท้จริงที่ทำให้ต้องมีเหตุผลที่แท้จริงว่าทำไมต้องทำสิ่งนี้ อย่างไรก็ตามคำตอบเกี่ยวกับการเป็น pre-ANSI C ที่กำหนดเอง (อย่างน้อยในบรรดาโปรแกรมเมอร์) ดูเหมือนจะเป็นเหตุผลที่ใช้ในกรณีของฉัน จุดที่ดีมากมายจากคนมากมายที่นี่ ขอบคุณสำหรับการสนับสนุนของคุณ


13
"อะไรคือจุดที่เตือนให้เรานึกถึงชนิดข้อมูลที่ถูกต้องก่อนที่จะปล่อยให้เป็นอิสระ?" อาจจะรู้ว่าหน่วยความจำจะถูกปลดปล่อย?
m0skit0

12
@Codor คอมไพเลอร์ไม่ทำการยกเลิกการจัดสรรระบบปฏิบัติการทำ
m0skit0

20
@ m0skit0 "อาจจะรู้ว่าหน่วยความจำจะได้รับอิสระมากแค่ไหน?" ประเภทไม่จำเป็นต้องรู้ว่าฟรี เพี้ยนด้วยเหตุผลนั้นมีเพียงการเข้ารหัสที่ไม่ดี
694733

9
@ m0skit0 การคัดเลือกเพื่อประโยชน์ในการอ่านนั้นเป็นการเขียนโค้ดที่ไม่ดีอยู่เสมอเนื่องจากการคัดเลือกจะเปลี่ยนวิธีการตีความประเภทและอาจซ่อนข้อผิดพลาดร้ายแรง เมื่อจำเป็นต้องอ่านความคิดเห็นจะดีกว่า
user694733

66
ในสมัยโบราณเมื่อไดโนเสาร์เดินแผ่นดินและเขียนโปรแกรมหนังสือผมเชื่อว่าไม่มีvoid*ใน pre-C มาตรฐาน char*แต่เพียง ดังนั้นหากการค้นพบทางโบราณคดีของคุณเปิดเผยรหัสที่หล่อพารามิเตอร์ให้เป็นอิสระ () ฉันเชื่อว่ามันจะต้องมาจากช่วงเวลานั้นหรือเขียนโดยสิ่งมีชีวิตในเวลานั้น ฉันไม่สามารถหาแหล่งที่มาสำหรับเรื่องนี้ได้ดังนั้นฉันจะละเว้นจากการตอบรับ
Lundin

คำตอบ:


171

constหล่ออาจจะต้องแก้ไขคำเตือนคอมไพเลอร์ชี้ถ้ามี นี่คือตัวอย่างของรหัสที่ทำให้เกิดการเตือนโดยไม่ส่งอาร์กิวเมนต์ของฟรี:

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

และคอมไพเลอร์ (gcc 4.8.3) พูดว่า:

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;

หากคุณใช้free((float*) velocity);คอมไพเลอร์หยุดบ่น


2
@ m0skit0 ที่ไม่ได้อธิบายว่าทำไมใครบางคนถึงจะลงมือfloat*ก่อนที่จะพ้น ฉันลองfree((void *)velocity);กับ gcc 4.8.3 แน่นอนว่ามันจะไม่ทำงานกับผู้แปลโบราณ
Manos Nikolaidis

54
แต่ทำไมคุณต้องจัดสรรหน่วยความจำคงที่แบบไดนามิก? คุณไม่สามารถใช้มันได้!
Nils_M

33
@Nils_M เป็นตัวอย่างที่ทำให้เข้าใจง่าย สิ่งที่ฉันทำในรหัสจริงในฟังก์ชั่นคือการจัดสรรหน่วยความจำที่ไม่ใช่ const, กำหนดค่า, ส่งไปยังตัวชี้ const และส่งกลับ ขณะนี้มีตัวชี้ไปยังหน่วยความจำ const ที่กำหนดไว้ล่วงหน้าว่ามีคนว่าง
Manos Nikolaidis

2
ตัวอย่าง :“ รูทีนย่อยเหล่านี้ส่งคืนสตริงในหน่วยความจำ malloc'ed ใหม่ซึ่งชี้โดย * stringValueP ซึ่งคุณจะต้องว่างในที่สุด บางครั้งฟังก์ชั่นระบบปฏิบัติการที่คุณใช้เพื่อเพิ่มหน่วยความจำได้รับการประกาศว่าจะใช้ตัวชี้ไปยังสิ่งที่ไม่คงที่ตามอาร์กิวเมนต์ดังนั้น * stringValueP จึงเป็นตัวชี้ไปยังกลุ่ม”
Carsten S

3
ผิดพลาดถ้าฟังก์ชั่นใช้const char *pเป็นอาร์กิวเมนต์แล้วปลดปล่อยมันสิ่งที่ถูกต้องที่จะทำไม่ได้ที่จะโยนpไปchar*ก่อนโทรฟรี มันจะไม่ประกาศว่าเป็นconst char *pสิ่งที่เกิดขึ้นตั้งแต่แรกเพราะมันจะปรับเปลี่ยน *pและควรได้รับการประกาศตาม (และถ้าใช้ตัวชี้ const แทนตัวชี้ไปยัง const int *const pคุณไม่จำเป็นต้องร่ายเนื่องจากมันถูกกฎหมายจริง ๆ และทำงานได้ดีโดยไม่ต้องร่าย)
Ray

59

pre-standard C ไม่มีvoid*แต่อย่างเดียวchar*ดังนั้นคุณต้องส่งพารามิเตอร์ทั้งหมดที่ส่งผ่าน หากคุณเจอรหัส C โบราณคุณอาจพบกับการปลดเปลื้องดังกล่าว

คำถามที่คล้ายกันที่มีการอ้างอิงคำถามที่คล้ายกันที่มีการอ้างอิง

เมื่อ C มาตรฐานครั้งแรกที่ได้รับการปล่อยตัวต้นแบบสำหรับ malloc และฟรีเปลี่ยนจากการมีchar*กับvoid*ที่พวกเขายังคงมีอยู่ในปัจจุบัน

และแน่นอนในมาตรฐาน C ซึ่งการปลดเปลื้องดังกล่าวไม่จำเป็นและเป็นอันตรายต่อความสามารถในการอ่าน


23
แต่ทำไมคุณถึงโต้แย้งfreeว่าเป็นประเภทเดียวกันกับที่มีอยู่แล้ว?
jwodder

4
@ chux ปัญหาของ pre-standard ก็คือ: ไม่มีข้อผูกมัดอะไรเลย ผู้คนเพิ่งชี้ไปที่หนังสือ K&R สำหรับศีลเพราะนั่นเป็นสิ่งเดียวที่พวกเขามี และอย่างที่เราเห็นได้จากหลาย ๆ ตัวอย่างใน K&R 2nd edition ตัวเอง K&R เองก็สับสนเกี่ยวกับการใช้พารามิเตอร์ในการfreeทำงานในมาตรฐาน C (คุณไม่จำเป็นต้องแสดง) ฉันยังไม่ได้อ่านฉบับที่ 1 ดังนั้นฉันจึงไม่สามารถบอกได้ว่าพวกเขาสับสนในยุค 80 ก่อนเวลามาตรฐานเช่นกัน
Lundin

7
Pre-standard C ไม่มีvoid*แต่มันก็ไม่ได้มีฟังก์ชั่นต้นแบบเช่นกันดังนั้นการคัดเลือกอากิวเมนต์จึงfreeยังไม่จำเป็นแม้แต่ใน K&R (สมมติว่าตัวชี้ข้อมูลทุกประเภทใช้การแทนแบบเดียวกัน)
เอียนแอ็บบอต

6
ด้วยเหตุผลหลายข้อที่ระบุไว้ในความคิดเห็นแล้วฉันไม่คิดว่าคำตอบนี้สมเหตุสมผล
. GitHub หยุดช่วยน้ำแข็ง

4
ฉันไม่เห็นว่าคำตอบนี้จะตอบสิ่งที่เกี่ยวข้องได้อย่างไร คำถามเดิมที่เกี่ยวข้องกับการปลดเปลื้องกับประเภทอื่น ๆ ไม่เพียง char *แต่จะ สิ่งที่รู้สึกว่ามันจะทำให้ในคอมไพเลอร์เก่าโดยไม่void? สิ่งที่ปลดเปลื้องจะประสบความสำเร็จ?
AnT

34

นี่คือตัวอย่างที่ฟรีจะล้มเหลวโดยไม่ต้องส่ง:

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 :)

ใน C คุณสามารถรับคำเตือน (มีคำเตือนใน VS2012) ใน C ++ คุณจะได้รับข้อผิดพลาด

กรณีที่หายากกันการคัดเลือกเพียง bloats รหัส ...

แก้ไข: ฉันใช้เพื่อvoid*ไม่int*ให้สาธิตความล้มเหลว มันจะทำงานเช่นเดียวกับที่int*จะถูกแปลงเป็นvoid*โดยปริยาย เพิ่มint*รหัสแล้ว


โปรดทราบว่าในรหัสที่โพสต์ในคำถามที่บรรยากาศจะไม่ไปvoid *แต่และfloat * char *การปลดเปลื้องเหล่านั้นไม่ได้เป็นเพียงสิ่งภายนอก แต่มันผิด
Andrew Henle

1
คำถามนี้เกี่ยวกับสิ่งตรงกันข้าม
m0skit0

1
ฉันไม่เข้าใจคำตอบ ในแง่ใดจะfree(p)ล้มเหลว มันจะให้ข้อผิดพลาดของคอมไพเลอร์หรือไม่?
Codor

1
นี่เป็นจุดที่ดี เช่นเดียวกันกับพconstอยน์เตอร์ของคุณสมบัติ
Lundin

2
volatileมีอยู่ตั้งแต่ C เป็นมาตรฐานหากไม่นาน มันก็ไม่ได้เพิ่มขึ้นใน C99
. GitHub หยุดช่วยน้ำแข็ง

30

เหตุผลเก่า: 1. เมื่อใช้free((sometype*) ptr)รหัสจะมีความชัดเจนเกี่ยวกับประเภทของตัวชี้ที่ควรพิจารณาว่าเป็นส่วนหนึ่งของการfree()โทร หล่ออย่างชัดเจนจะเป็นประโยชน์เมื่อfree()ถูกแทนที่ด้วย DIY_free()(ทำมันด้วยตัวเอง)

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

วิธี A DIY_free()คือ (คือ) โดยเฉพาะอย่างยิ่งในโหมดดีบักเพื่อทำการวิเคราะห์แบบรันไทม์ของตัวชี้ที่ถูกปล่อยให้เป็นอิสระ มักจะจับคู่กับ a DIY_malloc()เพื่อเพิ่ม sententials จำนวนหน่วยความจำที่ใช้ทั่วโลกเป็นต้นกลุ่มของฉันใช้เทคนิคนี้เป็นเวลาหลายปีก่อนที่เครื่องมือสมัยใหม่จะปรากฏขึ้น มันมีความจำเป็นที่ไอเท็มที่เป็นอิสระจะถูกส่งไปยังประเภทนั้นได้รับการจัดสรรในขั้นต้น

  1. เมื่อใช้เวลาหลายชั่วโมงในการติดตามปัญหาหน่วยความจำ ฯลฯ เทคนิคเล็ก ๆ น้อย ๆ เช่นการพิมพ์แบบฟรีจะช่วยในการค้นหาและ จำกัด การดีบัก

โมเดิร์น: การหลีกเลี่ยงconstและvolatileคำเตือนการแก้ไขโดยManos ดิ @และ@egur คิดว่าฉันจะทราบผลกระทบของ 3 รอบคัดเลือก : const, และvolatilerestrict

[แก้ไข] เพิ่มchar * restrict *rp2ต่อ@R .. ความคิดเห็น

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;
}

3
restrictเป็นเพียงปัญหาที่ไม่ได้เพราะมันอยู่ที่ไหน - มันส่งผลกระทบต่อวัตถุที่rpไม่ใช่แบบชี้ไปที่ หากคุณมีchar *restrict *rpแล้วมันจะสำคัญ
. GitHub หยุดช่วยน้ำแข็ง

16

นี่คือสมมติฐานทางเลือกอื่น

เราได้รับแจ้งว่าโปรแกรมนี้ถูกเขียนขึ้นล่วงหน้า C89 ซึ่งหมายความว่ามันไม่สามารถทำงานร่วมกับต้นแบบบางอย่างfreeได้เพราะไม่เพียง แต่ไม่มีสิ่งเช่นconstหรือvoid *ก่อน C89 เท่านั้นไม่มีสิ่งเช่นนั้นต้นแบบการทำงานก่อนที่จะ C89 stdlib.hตัวเองเป็นสิ่งประดิษฐ์ของคณะกรรมการ หากส่วนหัวของระบบใส่ใจที่จะประกาศfreeเลยพวกเขาจะทำอย่างนี้:

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

ตอนนี้จุดสำคัญที่นี่คือตัวตนของต้นแบบฟังก์ชั่นหมายถึงคอมไพเลอร์ก็ไม่มีการตรวจสอบชนิดของข้อโต้แย้ง มันใช้การเลื่อนระดับอาร์กิวเมนต์เริ่มต้น (รายการเดียวกันที่ยังใช้กับการเรียกฟังก์ชัน Variadic) และนั่นก็คือ ความรับผิดชอบในการสร้างข้อโต้แย้งที่ callsite แต่ละแถวสอดคล้องกับความคาดหวังของผู้จัดวางทั้งหมดกับโปรแกรมเมอร์

อย่างไรก็ตามสิ่งนี้ยังไม่ได้หมายความว่าจำเป็นที่จะต้องทำการโต้แย้งfreeในคอมไพเลอร์ K&R ส่วนใหญ่ ฟังก์ชั่นเช่น

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

ควรได้รับการรวบรวมอย่างถูกต้อง ดังนั้นฉันคิดว่าสิ่งที่เราได้ที่นี่คือโปรแกรมที่เขียนขึ้นเพื่อรับมือกับคอมไพเลอร์ buggy สำหรับสภาพแวดล้อมที่ผิดปกติ: ตัวอย่างเช่นสภาพแวดล้อมที่sizeof(float *) > sizeof(int)และคอมไพเลอร์จะไม่ใช้ระเบียบการโทรที่เหมาะสมสำหรับพอยน์เตอร์ ของการโทร

ฉันไม่ได้ตระหนักถึงสภาพแวดล้อมดังกล่าว แต่นั่นไม่ได้หมายความว่าไม่มี ผู้สมัครที่น่าจะเป็นไปได้มากที่สุดที่นึกถึงคือคอมไพเลอร์ "จิ๋ว C" ที่ถูกตัดลงสำหรับไมโคร 8- และ 16 บิตในช่วงต้นทศวรรษ 1980 ฉันยังไม่แปลกใจที่รู้ว่า Crays ยุคแรกมีปัญหาเช่นนี้


1
ครึ่งแรกฉันเห็นด้วยอย่างเต็มที่ และครึ่งหลังนั้นเป็นการคาดเดาที่น่าสนใจและน่าเชื่อถือ
chux - Reinstate Monica

9

ฟรีใช้ในพอยน์เตอร์ที่ไม่ใช่ const เท่านั้นเป็นพารามิเตอร์ ดังนั้นในกรณีของพอยน์เตอร์พอยต์จำเป็นต้องใช้การหล่ออย่างชัดเจนไปยังพอยน์เตอร์ที่ไม่ใช่ const

ไม่สามารถเพิ่มตัวชี้ const ใน C

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.