Aritmatika pointer untuk void pointer di C

177

Ketika pointer ke jenis tertentu (misalnya int, char, float, ..) bertambah, nilainya meningkat dengan ukuran jenis data. Jika sebuah voidpenunjuk yang menunjuk ke data ukuran xbertambah, bagaimana bisa sampai ke titik xbyte di depan? Bagaimana kompiler tahu untuk menambah xnilai pointer?

Siva Sankaran
sumber
kemungkinan duplikat Aritmatika Performa Pointer dibatalkan * di MSVC
Carl Norum
3
Pertanyaannya terdengar seolah-olah mengasumsikan bahwa kompiler (/ run-time) tahu jenis objek pointer diatur ke, dan menambahkan ukurannya ke pointer. Itu adalah kesalahpahaman yang lengkap: hanya tahu alamatnya.
PJTraill
2
"Jika sebuah voidpointer yang menunjuk ke data ukuran xbertambah, bagaimana ia bisa sampai titik xbyte di depan?" Tidak. Mengapa orang-orang yang memiliki pertanyaan semacam itu tidak dapat mengujinya sebelum bertanya - Anda tahu, setidaknya sampai titik minimum di mana mereka memeriksa apakah itu benar-benar dikompilasi, yang tidak. -1, tidak percaya ini dapat +100 dan -0.
underscore_d

Jawaban:

287

Kesimpulan akhir: aritmatika pada a void*adalah ilegal di C dan C ++.

GCC memungkinkannya sebagai ekstensi, lihat Aritmatika pada void- dan Function-Pointer (perhatikan bahwa bagian ini adalah bagian dari bab "C Extensions" pada manual). Dentang dan ICC kemungkinan memungkinkan void*aritmatika untuk tujuan kompatibilitas dengan GCC. Kompiler lain (seperti MSVC) melarang aritmatika aktif void*, dan GCC melarangnya jika -pedantic-errorsflag ditentukan, atau jika -Werror-pointer-arithflag ditentukan (flag ini berguna jika basis kode Anda juga harus dikompilasi dengan MSVC).

The C Standard Speaks

Kutipan diambil dari draft n1256.

Deskripsi standar tentang status operasi penjumlahan:

6.5.6-2: Sebagai tambahan, kedua operan harus memiliki tipe aritmatika, atau satu operan harus menjadi pointer ke tipe objek dan yang lainnya harus memiliki tipe integer.

Jadi, pertanyaannya di sini adalah apakah void*pointer ke "tipe objek", atau setara, apakah void"tipe objek". Definisi untuk "tipe objek" adalah:

6.2.5.1: Jenis dipartisi menjadi tipe objek (tipe yang sepenuhnya menggambarkan objek), tipe fungsi (tipe yang menggambarkan fungsi), dan tipe tidak lengkap (tipe yang menggambarkan objek tetapi kurang informasi yang dibutuhkan untuk menentukan ukurannya).

Dan standar didefinisikan voidsebagai:

6.2.5-19: voidJenis ini terdiri dari sekumpulan nilai kosong; ini adalah tipe tidak lengkap yang tidak dapat diselesaikan.

Karena voidini adalah tipe yang tidak lengkap, itu bukan tipe objek. Karenanya itu bukan operan yang valid untuk operasi penambahan.

Karena itu Anda tidak dapat melakukan aritmatika pointer pada voidpointer.

Catatan

Awalnya, ia berpikir bahwa void*aritmatika diizinkan, karena bagian-bagian dari standar C ini:

6.2.5-27: Sebuah pointer ke void harus memiliki persyaratan representasi dan perataan yang sama dengan pointer ke tipe karakter.

Namun,

Representasi yang sama dan persyaratan penyelarasan dimaksudkan untuk menyiratkan interchangeability sebagai argumen untuk fungsi, mengembalikan nilai dari fungsi, dan anggota serikat pekerja.

Jadi ini berarti yang printf("%s", x)memiliki arti yang sama apakah xmemiliki tipe char*atau void*, tetapi itu tidak berarti bahwa Anda dapat melakukan aritmatika pada a void*.

Catatan editor: Jawaban ini telah diedit untuk mencerminkan kesimpulan akhir.

Sadeq
sumber
7
Dari standar C99: (6.5.6.2) Sebagai tambahan, salah satu dari kedua operand harus memiliki tipe aritmatika, atau satu operand harus menjadi pointer ke tipe objek dan yang lainnya harus memiliki tipe integer. (6.2.5.19) Jenis void terdiri dari seperangkat nilai kosong; ini adalah tipe tidak lengkap yang tidak dapat diselesaikan. Saya pikir itu menjelaskan bahwa void*pointer aritmatika tidak diperbolehkan. GCC memiliki ekstensi yang memungkinkan untuk melakukan ini.
Pekerjaan
1
jika Anda tidak lagi menganggap jawaban Anda berguna, Anda bisa menghapusnya.
caf
1
Jawaban ini bermanfaat meskipun terbukti salah karena mengandung bukti konklusif bahwa batal pointer tidak dimaksudkan untuk aritmatika.
Ben Flynn
1
Ini adalah jawaban yang baik, memiliki kesimpulan yang tepat dan kutipan yang diperlukan, tetapi orang-orang yang sampai pada pertanyaan ini sampai pada kesimpulan yang salah karena mereka tidak membaca bagian bawah dari jawabannya. Saya telah mengedit ini untuk membuatnya lebih jelas.
Dietrich Epp
1
Dentang dan ICC tidak mengizinkan void*aritmatika (setidaknya secara default).
Sergey Podobry
61

Aritmatika pointer tidak diizinkan pada void*pointer.

Pekerjaan
sumber
16
+1 Aritmatika pointer hanya ditentukan untuk pointer ke tipe objek (lengkap) . voidadalah tipe tidak lengkap yang tidak pernah dapat diselesaikan dengan definisi.
schot
1
@ schot: Tepat. Selain itu, aritmatika pointer hanya didefinisikan pada pointer ke elemen objek array dan hanya jika hasil operasi akan menjadi pointer ke elemen dalam array yang sama atau melewati elemen terakhir array itu. Jika kondisi tersebut tidak terpenuhi, itu adalah perilaku yang tidak terdefinisi. (Dari standar C99 6.5.6.8)
Ayub
1
Tampaknya tidak demikian halnya dengan gcc 7.3.0. Compiler menerima p + 1024, di mana p tidak berlaku *. Dan hasilnya sama dengan ((char *) p) + 1024
zzz777
@ zzz777 itu adalah ekstensi GCC, lihat tautan di jawaban pilihan teratas.
Ruslan
19

melemparkannya ke pointer char peningkatan incaran Anda maju x byte di depan.

Gobblement Ultimate
sumber
4
Mengapa mengganggu? Hanya melemparkannya ke jenis yang tepat - yang harus selalu diketahui - dan menambahnya dengan 1. Melemparkan ke char, menambah dengan x, dan kemudian menafsirkan kembali nilai baru karena beberapa jenis lainnya adalah perilaku yang tidak berguna dan tidak terdefinisi.
underscore_d
9
Jika Anda menulis fungsi sortir, yang menurut man 3 qsortseharusnya memiliki void qsort(void *base, size_t nmemb, size_t size, [snip]), maka Anda tidak memiliki cara untuk mengetahui "tipe yang tepat"
alisianoi
16

Standar C tidak memungkinkan aritmetika void pointer. Namun, GNU C diperbolehkan dengan mempertimbangkan ukuran void tersebut 1.

Standar C11 §6.2.5

Paragraf - 19

The voidTipe terdiri dari himpunan kosong dari nilai-nilai; itu adalah tipe objek tidak lengkap yang tidak dapat diselesaikan.

Program berikut berfungsi dengan baik di kompiler 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;
}

Mungkin kompiler lain menghasilkan kesalahan.

msc
sumber
14

Anda tidak dapat melakukan aritmatika penunjuk pada void *tipe, untuk alasan yang tepat ini!

Oliver Charlesworth
sumber
8

Anda harus melemparkannya ke jenis pointer lain sebelum melakukan aritmatika pointer.

Jakob
sumber
7

Pointer kosong dapat mengarah ke potongan memori apa pun. Karenanya kompiler tidak tahu berapa byte yang bertambah / berkurang ketika kita mencoba pointer aritmatika pada void pointer. Oleh karena itu void pointer harus menjadi typecast pertama ke jenis yang diketahui sebelum mereka dapat terlibat dalam aritmatika pointer apa pun.

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.
tchrist
sumber
-1

Kompiler tahu berdasarkan tipe pemain. Diberikan void *x:

  • x+1menambahkan satu byte ke x, pointer menuju ke bytex+1
  • (int*)x+1menambahkan sizeof(int)byte, pointer menuju ke bytex + sizeof(int)
  • (float*)x+1addres sizeof(float)byte, dll.

Meskipun item pertama tidak portabel dan bertentangan dengan Galateo dari C / C ++, itu tetap benar dalam bahasa C, artinya ia akan dikompilasi ke sesuatu pada kebanyakan kompiler yang mungkin memerlukan bendera yang sesuai (seperti -Wpointer-arith)

rytis
sumber
2
Althought the first item is not portable and is against the Galateo of C/C++Benar. it is nevertheless C-language-correctSalah. Ini adalah pemikiran ganda! Aritmatika pointer aktif void *secara sintaksis, tidak boleh dikompilasi, dan menghasilkan perilaku yang tidak terdefinisi jika itu. Jika seorang programmer yang ceroboh dapat membuatnya dikompilasi dengan menonaktifkan beberapa peringatan, itu bukan alasan.
underscore_d
@underscore_d: Saya pikir beberapa kompiler digunakan untuk memungkinkannya sebagai ekstensi, karena jauh lebih nyaman daripada harus dilemparkan unsigned char*ke misalnya menambahkan sizeofnilai ke pointer.
supercat