เลขคณิตของตัวชี้สำหรับตัวชี้โมฆะใน C


176

เมื่อมีการชี้ไปยังประเภทใดประเภทหนึ่ง (พูดint, char, float, .. ) จะเพิ่มขึ้นค่าของมันจะเพิ่มขึ้นตามขนาดของชนิดข้อมูลว่า หากvoidตัวชี้ที่ชี้ไปยังข้อมูลขนาดxเพิ่มขึ้นมันจะไปยังจุดxไบต์ข้างหน้าได้อย่างไร คอมไพเลอร์รู้ได้อย่างไรว่าจะเพิ่มxมูลค่าของตัวชี้?



3
คำถามฟังดูเหมือนว่าคอมไพเลอร์ (/ รันไทม์) รู้ว่าวัตถุชนิดใดที่ตัวชี้ถูกตั้งค่าและเพิ่มขนาดของตัวชี้ นั่นคือความเข้าใจผิดที่สมบูรณ์: รู้เพียงที่อยู่
PJTraill

2
"ถ้าvoidตัวชี้ที่ชี้ไปยังข้อมูลขนาดxเพิ่มขึ้นมันจะชี้ไปยังจุดต่อxไปได้อย่างไร" มันไม่ได้ ทำไมคนที่มีคำถามเช่นนี้ไม่สามารถทดสอบพวกเขาก่อนถามได้ - อย่างน้อยก็ถึงขั้นต่ำสุดที่พวกเขาตรวจสอบว่ามันรวบรวมจริงหรือไม่ -1 ไม่น่าเชื่อว่านี่จะเป็น +100 และ -0
underscore_d

คำตอบ:


287

ข้อสรุปสุดท้าย: เลขคณิตบน a void*นั้นผิดกฎหมายทั้งใน C และ C ++

GCC อนุญาตให้เป็นส่วนขยายให้ดูที่การคำนวณทางคณิตศาสตร์voidและตัวชี้ฟังก์ชั่น (โปรดทราบว่าส่วนนี้เป็นส่วนหนึ่งของบท "C ​​Extensions" ของคู่มือ) เสียงดังกราวและ ICC น่าจะอนุญาตให้ใช้void*เลขคณิตสำหรับจุดประสงค์ในการเข้ากันได้กับ GCC คอมไพเลอร์อื่น ๆ (เช่น MSVC) ไม่อนุญาตให้ใช้การคำนวณทางคณิตศาสตร์void*และ GCC ไม่อนุญาตหากระบุ-pedantic-errorsแฟล็กหรือหากระบุ-Werror-pointer-arithแฟล็ก (แฟล็กนี้มีประโยชน์หากรหัสฐานของคุณต้องคอมไพล์ด้วย MSVC)

มาตรฐาน C พูด

คำพูดนำมาจากร่าง n1256

คำอธิบายมาตรฐานของสถานะการดำเนินการเพิ่มเติม:

6.5.6-2: สำหรับการเพิ่มเติมตัวถูกดำเนินการทั้งสองจะต้องมีประเภทคณิตศาสตร์หรือตัวถูกดำเนินการหนึ่งจะเป็นตัวชี้ไปยังประเภทวัตถุและอื่น ๆ จะต้องมีประเภทจำนวนเต็ม

ดังนั้นคำถามที่นี่คือว่าvoid*เป็นตัวชี้ไปที่ "ประเภทวัตถุ" หรือเทียบเท่าว่าvoidเป็น "ประเภทวัตถุ" คำจำกัดความของ "ประเภทวัตถุ" คือ:

6.2.5.1: ประเภทถูกแบ่งพาร์ติชันออกเป็นประเภทออบเจกต์ (ชนิดที่อธิบายถึงวัตถุทั้งหมด), ประเภทฟังก์ชั่น (ประเภทที่อธิบายฟังก์ชั่น) และประเภทที่ไม่สมบูรณ์ (ประเภทที่อธิบายถึงวัตถุ แต่ไม่มีข้อมูลที่จำเป็นในการกำหนดขนาด)

และมาตรฐานกำหนดvoidเป็น:

6.2.5-19: voidประเภทประกอบด้วยชุดค่าที่ว่างเปล่า; เป็นประเภทที่ไม่สมบูรณ์ที่ไม่สามารถดำเนินการให้เสร็จสิ้นได้

เนื่องจากvoidเป็นชนิดที่ไม่สมบูรณ์จึงไม่ใช่ชนิดวัตถุ ดังนั้นจึงไม่ใช่ตัวถูกดำเนินการที่ถูกต้องสำหรับการดำเนินการเพิ่มเติม

ดังนั้นคุณไม่สามารถทำการคำนวณทางคณิตศาสตร์ของvoidตัวชี้ได้

หมายเหตุ

เดิมทีคิดว่าvoid*อนุญาตให้ใช้เลขคณิตเนื่องจากส่วนเหล่านี้ของมาตรฐาน C:

6.2.5-27: ตัวชี้เป็นโมฆะจะต้องมีข้อกำหนดการเป็นตัวแทนและการจัดตำแหน่งเดียวกัน กับตัวชี้ไปยังชนิดอักขระ

อย่างไรก็ตาม

ข้อกำหนดการเป็นตัวแทนและการจัดตำแหน่งเดียวกัน นั้นหมายถึงการบ่งบอกถึงความสามารถในการแลกเปลี่ยนระหว่างกันกับฟังก์ชั่นการคืนค่าจากฟังก์ชั่นและสมาชิกของสหภาพ

ดังนั้นวิธีการที่printf("%s", x)มีความหมายเหมือนกันไม่ว่าxมีประเภทchar*หรือแต่มันไม่ได้หมายความว่าคุณสามารถทำเลขคณิตบนvoid*void*

หมายเหตุบรรณาธิการ:คำตอบนี้ได้รับการแก้ไขเพื่อให้สะท้อนถึงข้อสรุปสุดท้าย


7
จากมาตรฐาน C99: (6.5.6.2) สำหรับการเพิ่มเติมทั้งตัวถูกดำเนินการจะต้องมีประเภทคณิตศาสตร์หรือหนึ่งตัวถูกดำเนินการจะเป็นตัวชี้ไปยังประเภทวัตถุและอื่น ๆ จะต้องมีประเภทจำนวนเต็ม (6.2.5.19) ประเภทของโมฆะประกอบด้วยชุดของค่าที่ว่างเปล่า; เป็นประเภทที่ไม่สมบูรณ์ที่ไม่สามารถดำเนินการให้เสร็จสิ้นได้ ฉันคิดว่ามันชัดเจนว่าvoid*ไม่อนุญาตให้ใช้ตัวชี้ทางคณิตศาสตร์ GCC มีส่วนขยายที่อนุญาตให้ทำได้
งาน

1
หากคุณไม่คิดว่าคำตอบของคุณจะมีประโยชน์อีกต่อไปคุณสามารถลบได้
คาเฟ่

1
คำตอบนี้มีประโยชน์แม้ว่ามันจะพิสูจน์ว่าผิดเพราะมันมีข้อสรุปที่เป็นโมฆะชี้ไม่ได้มีไว้สำหรับการคำนวณ
Ben Flynn

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

1
เสียงดังกราวและ ICC ไม่อนุญาตให้ใช้void*เลขคณิต (อย่างน้อยก็ตามค่าเริ่มต้น)
Sergey Podobry

61

ไม่อนุญาตให้ใช้ตัวชี้ทางคณิตศาสตร์ในพvoid*อยน์เตอร์


16
+1 คำนวณตัวชี้ถูกกำหนดไว้เฉพาะสำหรับตัวชี้ไปยัง (ฉบับสมบูรณ์) วัตถุประเภท voidเป็นประเภทที่ไม่สมบูรณ์ที่ไม่สามารถทำให้เสร็จสิ้นได้ด้วยคำจำกัดความ
schot

1
@schot: แน่นอน ยิ่งไปกว่านั้นการคำนวณทางคณิตศาสตร์ของพอยน์เตอร์นั้นถูกกำหนดไว้บนตัวชี้ไปยังองค์ประกอบของวัตถุอาเรย์เท่านั้นและหากผลลัพธ์ของการดำเนินการจะเป็นตัวชี้ไปยังองค์ประกอบในอาเรย์เดียวกันนั้นหรือผ่านองค์ประกอบสุดท้ายของอาเรย์นั้น หากไม่ปฏิบัติตามเงื่อนไขเหล่านั้นแสดงว่าเป็นพฤติกรรมที่ไม่ได้กำหนด (จาก C99 มาตรฐาน 6.5.6.8)
งาน

1
เห็นได้ชัดว่ามันไม่เป็นเช่นนั้นด้วย gcc 7.3.0 คอมไพเลอร์ยอมรับ p + 1024 โดยที่ p เป็นโมฆะ * และผลลัพธ์จะเหมือนกับ ((ถ่าน *) p) + 1024
zzz777

@ zzz777 เป็นส่วนขยาย GCC ดูลิงก์ในคำตอบที่ได้รับคะแนนสูงสุด
Ruslan

19

โยนมันไปที่ตัวชี้ถ่านการเพิ่มตัวชี้ของคุณไปข้างหน้า x ไบต์ล่วงหน้า


4
ทำไมต้องรำคาญ เพียงแค่โยนมันลงไปในประเภทที่ถูกต้องซึ่งควรรู้อยู่เสมอและเพิ่มขึ้นด้วย 1 การส่งไปที่การcharเพิ่มโดยการxตีความค่าใหม่อีกครั้งเนื่องจากบางประเภทอื่นนั้นมีพฤติกรรมที่ไม่มีจุดหมายและไม่ได้กำหนด
underscore_d

9
หากคุณกำลังเขียนฟังก์ชั่นการเรียงลำดับของคุณซึ่งตามที่man 3 qsortควรจะมีvoid qsort(void *base, size_t nmemb, size_t size, [snip])แล้วคุณไม่มีทางรู้ว่า "ประเภทที่ถูกต้อง"
alisianoi

15

มาตรฐาน Cไม่อนุญาตให้ถือเป็นโมฆะเลขคณิตชี้ อย่างไรก็ตามGNU Cที่ได้รับอนุญาตโดยพิจารณาขนาดของโมฆะ1คือ

C11 มาตรฐาน§6.2.5

ย่อหน้า - 19

voidประเภทประกอบด้วยเซตว่างของค่า; เป็นประเภทวัตถุที่ไม่สมบูรณ์ที่ไม่สามารถทำให้เสร็จ

โปรแกรมต่อไปนี้ทำงานได้ดีในคอมไพเลอร์ GCC

#include<stdio.h>

int main()
{
    int arr[2] = {1, 2};
    void *ptr = &arr;
    ptr = ptr + sizeof(int);
    printf("%d\n", *(int *)ptr);
    return 0;
}

อาจเป็นคอมไพเลอร์อื่น ๆ ที่สร้างข้อผิดพลาด



8

คุณต้องโยนมันไปที่ตัวชี้ประเภทอื่นก่อนที่จะทำการคำนวณทางคณิตศาสตร์


7

ตัวชี้โมฆะสามารถชี้ไปที่หน่วยความจำอันใดอันหนึ่ง ดังนั้นคอมไพเลอร์ไม่ทราบจำนวนไบต์ที่จะเพิ่ม / ลดลงเมื่อเราพยายามคำนวณเลขคณิตบนตัวชี้โมฆะ ดังนั้นโมฆะพอยน์เตอร์จะต้องเป็น typecast แรกเป็นประเภทที่รู้จักกันก่อนที่พวกเขาจะสามารถมีส่วนร่วมในการคำนวณทางคณิตศาสตร์ใด ๆ

void *p = malloc(sizeof(char)*10);
p++; //compiler does how many where to pint the pointer after this increment operation

char * c = (char *)p;
c++;  // compiler will increment the c by 1, since size of char is 1 byte.

-1

คอมไพเลอร์รู้ตามประเภทการส่ง รับvoid *x:

  • x+1เพิ่มหนึ่งไบต์ไปยังxตัวชี้ไปที่ไบต์x+1
  • (int*)x+1เพิ่มsizeof(int)ไบต์ตัวชี้ไปที่ไบต์x + sizeof(int)
  • (float*)x+1sizeof(float)ไบต์ที่อยู่ฯลฯ

แม้ว่ารายการแรกจะไม่พกพาได้และต่อต้าน Galateo ของ C / C ++ แต่ก็เป็นภาษา C ที่ถูกต้องซึ่งหมายความว่ามันจะรวบรวมบางสิ่งบางอย่างในคอมไพเลอร์ส่วนใหญ่อาจจำเป็นต้องใช้ธงที่เหมาะสม (เช่น -Wpointer-arith)


2
Althought the first item is not portable and is against the Galateo of C/C++จริง it is nevertheless C-language-correctเท็จ นี่คือความคิดที่สอง! การคำนวณทางคณิตศาสตร์ของตัวชี้void *นั้นผิดกฎหมาย syntactically ไม่ควรรวบรวมและสร้างพฤติกรรมที่ไม่ได้กำหนดถ้ามัน หากโปรแกรมเมอร์ที่ประมาทสามารถรวบรวมได้โดยปิดใช้งานคำเตือนบางอย่างนั่นก็ไม่ใช่ข้อแก้ตัว
underscore_d

@underscore_d: ฉันคิดว่าคอมไพเลอร์บางตัวเคยอนุญาตให้ใช้เป็นส่วนขยายเนื่องจากสะดวกกว่าการส่งunsigned char*ไปเช่นเพิ่มsizeofค่าให้กับตัวชี้
supercat
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.