Dalam C, apakah * operator, atau bagian dari tipe dalam deklarasi?

9

Dalam C, *disebut operator tidak langsung atau operator dereference. Saya mengerti cara kerjanya saat digunakan dalam pernyataan. Masuk akal untuk menulis *patau * p, mengingat itu adalah operator unary.

Namun, terkadang dalam deklarasi, a *digunakan.

void move(int *units) { ... }

atau

int *p = &x;

Rasanya aneh bagi saya bahwa operator digunakan di sini. Anda tidak dapat melakukan hal-hal seperti

void move(int units++) { ... }

dimana ++juga merupakan operator unary. Apakah ini *juga operator tipuan, atau apakah ini *memiliki arti yang berbeda, di mana ia mengatakan sesuatu tentang tipe pointer? (Jika ini masalahnya, saya mempertimbangkan untuk menggunakan misalnya int* unitsdalam deklarasi dan int x = *p;sebaliknya untuk kejelasan.)

Dalam hal ini jawaban dikatakan bahwa

Standar C hanya mendefinisikan dua arti bagi operator *:

  • operator tipuan
  • operator perkalian

Saya juga melihat orang mengklaim bahwa *itu sebenarnya bagian dari tipe. Ini membingungkan saya.

Torm
sumber
1
Di void move(int *units) { ... }, itu adalah operator tidak langsung. Dianggap sebagai bagian dari tipe, itu juga dapat ditulis void move(int* units) { ... }, meskipun saya lebih suka gaya sebelumnya. Anda membaca keduanya sebagai "int pointer." Lihat juga stackoverflow.com/a/8911253
Robert Harvey

Jawaban:

16

Potongan-potongan terkecil dari bahasa C adalah token leksikal , seperti kata kunci (misalnya int, if, break), pengidentifikasi (misalnya move, units) dan lain-lain.

*adalah elemen leksikal yang disebut punctuator (seperti misalnya {}()+). Sebuah punctuator dapat memiliki arti berbeda tergantung pada bagaimana ia digunakan:

Punctuator adalah simbol yang memiliki signifikansi sintaksis dan semantik yang independen. Tergantung pada konteksnya, ia dapat menentukan operasi yang akan dilakukan (...) dalam hal ini dikenal sebagai operator

Dalam C11 standar bab 6.5 pada ekspresi, *didefinisikan sebagai operator unary (bagian 6.5.3.2, untuk tipuan) dan sebagai operator multiplikasi (bagian 6.5.5), persis seperti yang telah Anda jelaskan. Tetapi *hanya diartikan sebagai operator sesuai dengan aturan tata bahasa yang membuat ekspresi yang valid.

Tapi ada juga bab 6.2 tentang konsep, yang menjelaskan bahwa pointer ke tipe adalah tipe turunan. Bagian 6.7.6.1 tentang deklarator pointer lebih tepat:

jika, dalam deklarasi '' T D1 '', D1 memiliki bentuk
* tipe-kualifikasi-daftar-opt D
dan tipe yang ditentukan untuk ident dalam deklarasi '' T D '' adalah '' turunan-deklarator-tipe-daftar T '', maka tipe yang ditentukan untuk ident adalah '' turunan-declarator-type-list type-kualifikasi-daftar pointer ke T ''.

Ini memang membuat *bagian dari jenis turunan ( *berarti ada "penunjuk", tetapi selalu perlu jenis lain untuk mengatakan apa jenisnya menunjuk ke sesuatu).

Catatan: Tidak ada kontradiksi dalam hal ini. Anda menggunakan elemen bahasa leksikal yang berbeda setiap hari ketika Anda berbicara bahasa Inggris: " pegangan " menunjuk sesuatu dan " menangani " berarti melakukan sesuatu, dua penggunaan tata bahasa yang sama sekali berbeda dari unsur leksikal yang sama.

Christophe
sumber
Itu berarti bahwa penafsiran ini hanya cara umum untuk melihatnya (dari jawaban lain): int * pernyataan ini mungkin ditafsirkan untuk dibaca: menyatakan variabel, bernama a, bahwa ketika dereferenced adalah tipe int. -Ketika pada kenyataannya * memiliki makna Independen dalam sebuah deklarasi
Torm
@ Persis tepatnya!
Christophe
7

Dalam C, tipuan dalam deklarasi lebih baik dibaca sebagai bagian dari variabel daripada bagian dari tipe. Jika saya punya:

int *a;

pernyataan ini mungkin ditafsirkan untuk membaca: menyatakan sebuah variabel, bernama a, bahwa ketika dereferenced adalah tipe int.

Ini penting ketika beberapa variabel dinyatakan sekaligus:

int *a, b;   (1)
int* a, b;   (2)

Kedua deklarasi sama, dan keduanya adan bbukan tipe yang sama - aadalah pointer ke int dan bint. Tetapi deklarasi (2) tampak seolah-olah "tipe pointer" adalah bagian dari tipe ketika sebenarnya deklarasi *a.

Erik
sumber
Itu poin yang sangat baik untuk mengemukakan ini, karena sering ada kebingungan antara 1 dan 2. Namun tipe a adalah "pointer to int", jadi bahkan jika * hanya diterapkan pada, pasti merupakan bagian dari jenis . Misalnya Anda dapat menggunakannya dalam typedef (yang tidak dapat Anda lakukan untuk penginisialisasi).
Christophe
Sedang mencari argumen yang baik dalam memutuskan antara kedua deklarasi, ini berhasil. Saya jarang mendeklarasikan variabel sebagai daftar, tetapi masih, Mulai sekarang saya akan mengadopsi bentuk pertama sebagai yang paling jelas dalam semua kasus. Terima kasih!
Newtopian
4

Untuk menambah jawaban yang benar lainnya:

Itu hanya pernyataan deklarasi yang mencerminkan potensi ekspresi penggunaan. IIRC, kupikir Kernigan atau Ritchie menyebutnya eksperimen! Ini beberapa teks yang mendukung:

Kembali di tahun 70-an, ketika Kernighan dan Ritchie menulis The C Programming Language, mereka cukup jujur ​​tentang bagaimana deklarasi dapat dengan cepat menjadi tidak dapat dibaca oleh mata yang tidak terlatih:

C kadang-kadang dikecam untuk sintaks deklarasi, terutama yang melibatkan pointer ke fungsi. Sintaks adalah upaya untuk membuat deklarasi dan penggunaannya setuju ; ini bekerja dengan baik untuk kasus-kasus sederhana, tetapi bisa membingungkan untuk yang lebih sulit, karena deklarasi tidak dapat dibaca dari kiri ke kanan, dan karena tanda kurung terlalu digunakan. [...]

(Penekanan saya.)

http://codinghighway.com/2013/12/29/the-absolute-definitive-guide-to-decipher-c-declarations/

Jadi, ya, Anda benar, ini memang operator yang sama yang diterapkan pada ekspresi deklarasi, meskipun seperti yang Anda amati hanya beberapa operator yang masuk akal (mis. ++ tidak).

Bintang seperti itu memang bagian dari tipe variabel individual yang dideklarasikan; namun, sebagaimana dicatat oleh poster lain, poster tidak (lebih tepatnya, tidak bisa) dibawa ke variabel lain yang dideklarasikan pada waktu yang sama (yaitu dalam pernyataan deklarasi yang sama) - setiap variabel dimulai dengan jenis basis yang sama seperti target (akhirnya), dan kemudian mendapatkan penunjuk, array, dan operator fungsi (peluang untuk menerapkan atau tidak) sendiri untuk menyelesaikan tipenya.


Inilah sebabnya mengapa programmer yang berpengalaman cenderung lebih suka (1) int *p;vs. int* p;dan (2) mendeklarasikan hanya satu variabel per deklarasi, meskipun bahasa lebih memungkinkan.

Untuk menambahkan bahwa itu int (*p);adalah deklarasi hukum, orangtua ini hanya mengelompokkan dan dalam kasus sederhana ini, dan tidak melakukan apa pun yang berbeda int *p. Ini sama dengan mengatakan ekspresi (non-deklaratif) (i) >= (0)sama dengan i >= 0, karena Anda selalu dapat menambahkan secara legal ().

Saya menyebutkan bahwa sebagai contoh lain, meskipun, mengapa programmer lebih memilih salah satu bentuk dari yang lain, jadi pertimbangkan int (*p);vs int(* p);. Mungkin Anda bisa melihat bagaimana tanda kurung, tanda bintang, dll. Berlaku untuk variabel bukan tipe dasar (yaitu dengan menambahkan semua tanda kurung yang diizinkan).

Erik Eidt
sumber
1

Dalam deklarasi fungsi ini:

void move(int *units);

itu *adalah bagian dari tipe, yaitu int*. Itu bukan operator. Itulah sebabnya banyak orang menulisnya sebagai:

void move(int* units);
BЈовић
sumber
1

Hanya *, [], dan ()operator memiliki makna dalam deklarasi (C ++ menambahkan &, tapi kami tidak akan masuk ke sini).

Dalam deklarasi

int *p;       

yang int-ness dari pditentukan oleh jenis specifier int, sedangkan pointer-ness pditentukan oleh deklarator *p .

The tipe dari padalah "pointer ke int"; tipe ini sepenuhnya ditentukan oleh kombinasi specifier tipe intplus deklarator *p.

Dalam deklarasi, deklarator memperkenalkan nama hal yang dideklarasikan ( p) bersama dengan informasi tipe tambahan yang tidak diberikan oleh specifier tipe ("pointer ke"):

T v;     // v is a single object of type T, for any type T
T *p;    // p is a pointer to T, for any type T
T a[N];  // a is an N-element array of T, for any type T
T f();   // f is a function returning T, for any type T

Ini penting - pointer-ness, array-ness, dan function-ness ditentukan sebagai bagian dari deklarator, bukan tipe specifier 1 . Jika kamu menulis

int* a, b, c;

itu akan diuraikan sebagai

int (*a), b, c;

jadi hanya aakan dinyatakan sebagai pointer ke int; bdan cdinyatakan sebagai ints biasa .

The *, [], dan ()operator dapat dikombinasikan untuk membuat jenis sewenang-wenang kompleks:

T *a[N];      // a is an N-element array of pointers to T
T (*a)[N];    // a is a pointer to an N-element array of T
T *(*f[N])(); // f is an N-element array of pointers to functions 
              // returning pointer to T

T *(*(*(*f)[N])())[M]  // f is a pointer to an N-element array of pointers
                       // to functions returning pointers to M-element
                       // arrays of pointers to T

Perhatikan bahwa *,, []dan ()patuhi aturan prioritas yang sama dalam deklarasi yang mereka lakukan dalam ekspresi. *a[N]diuraikan seperti *(a[N])dalam deklarasi dan ekspresi.

Hal yang sangat penting untuk disadari dalam hal ini adalah bahwa bentuk deklarasi cocok dengan bentuk ekspresi dalam kode. Kembali ke contoh asli kami, kami memiliki pointer ke integer bernama p. Jika kami ingin mengambil nilai integer itu, kami menggunakan *operator untuk dereferensi p, seperti:

x = *p;

Jenis ungkapannya *p adalah int, yang mengikuti dari deklarasi

int *p;

Demikian pula, jika kita memiliki array pointer doubledan kita ingin mengambil nilai tertentu, kita indeks ke dalam array dan melakukan dereferensi hasilnya:

y = *ap[i];

Sekali lagi, jenis ungkapannya *ap[i] adalah double, yang mengikuti dari deklarasi

double *ap[N];

Jadi mengapa tidak ++berperan dalam deklarasi seperti *, [], atau ()? Atau operator lain seperti +atau ->atau &&?

Yah, pada dasarnya, karena definisi bahasa mengatakannya. Itu hanya mengesampingkan *,, []dan ()untuk memainkan peran apa pun dalam deklarasi, karena Anda harus dapat menentukan jenis pointer, array, dan fungsi. Tidak ada tipe "increment-this" yang terpisah, jadi tidak perlu ++menjadi bagian dari deklarasi. Tidak ada "bitwise-ini" jenis, sehingga tidak perlu untuk unary &, |, ^, atau ~menjadi bagian dari deklarasi baik. Untuk jenis yang menggunakan .operator pemilihan anggota, kami menggunakan structdan uniontag dalam deklarasi. Untuk jenis yang menggunakan ->operator, kami menggunakan tag structdan unionbersama dengan *operator di deklarator.


  1. Tentu saja, Anda bisa membuat nama typedef untuk tipe pointer, array, dan fungsi
    typedef int *iptr;
    iptr a,b,c; // all three of a, b, and c are pointers to int
    tapi sekali lagi, itu adalah deklarator *iptr yang menentukan pointer-ness dari nama typedef.

John Bode
sumber
1

Standar C hanya mendefinisikan dua arti bagi *operator:

  • operator tipuan
  • operator perkalian

Memang benar ada dua arti bagi * operator . Tidak benar bahwa itu adalah satu-satunya makna * token .

Dalam deklarasi suka

int *p;

yang *tidak operator; itu bagian dari sintaksis suatu deklarasi.

Sintaks deklarasi C dimaksudkan untuk mencerminkan sintaks ekspresinya, sehingga deklarasi di atas dapat dibaca sebagai " *padalah tipe int", dari mana kita dapat menyimpulkan bahwa itu padalah tipe int*. Namun, ini bukan aturan yang tegas, dan itu tidak dinyatakan di manapun dalam standar. Alih-alih, sintaks deklarasi didefinisikan sendiri, secara terpisah dari sintaks ekspresi. Itulah sebabnya, misalnya, sebagian besar operator yang dapat muncul dalam ekspresi tidak dapat muncul dengan makna yang sama dalam deklarasi (kecuali jika mereka adalah bagian dari ekspresi yang merupakan bagian dari deklarasi, seperti +operator dalam int array[2+2];).

Sebuah kasus di mana "deklarasi cermin menggunakan" prinsip tidak cukup bekerja adalah:

int arr[10];

di mana arr[10]akan bertipe int jika ada .

Dan sebenarnya masih ada lagi penggunaan *token. Parameter array dapat dideklarasikan dengan [*]untuk mengindikasikan array panjang variabel. (Ini ditambahkan dalam C99.) Ini *bukan perkalian atau dereferensi. Saya menduga itu terinspirasi oleh sintaks shell wildcard.

Keith Thompson
sumber
Tempat yang lebih besar dari konsep "deklarasi mengikuti penggunaan" adalah dengan inisialisasi pointer. Efek dari deklarasi int *p = 0;terlihat mirip dengan penggunaan * p = 0; `tetapi artinya sangat berbeda.
supercat
@supercat: Tentu. inisialisasi selalu cocok dengan jenis objek (dalam hal ini int*).
Keith Thompson