Berapa banyak level pointer yang bisa kita miliki?

443

Berapa banyak pointer ( *) yang diizinkan dalam satu variabel?

Mari kita perhatikan contoh berikut ini.

int a = 10;
int *p = &a;

Demikian pula yang bisa kita miliki

int **q = &p;
int ***r = &q;

dan seterusnya.

Sebagai contoh,

int ****************zz;
Parag
sumber
582
Jika itu menjadi masalah nyata bagi Anda, Anda melakukan sesuatu yang sangat salah.
ThiefMaster
279
Anda dapat terus menambahkan level pointer hingga otak Anda meledak atau kompilernya meleleh - mana yang terjadi paling cepat.
JeremyP
47
Karena pointer ke pointer lagi, yah, hanya sebuah pointer, seharusnya tidak ada batas teoritis. Mungkin kompiler tidak akan mampu mengatasinya melampaui batas yang sangat tinggi, tapi yah ...
Christian Rau
73
dengan c ++ terbaru Anda harus menggunakan sesuatu sepertistd::shared_ptr<shared_ptr<shared_ptr<...shared_ptr<int>...>>>
josefx
44
@ josefx - ini menunjukkan masalah dalam standar C ++ - tidak ada cara untuk meningkatkan smart pointer ke power. Kita harus segera menuntut perpanjangan untuk mendukung misalnya (pow (std::shared_ptr, -0.3))<T> x;untuk -0,3 tingkat tipuan.
Steve314

Jawaban:

400

The Cmenspesifikasikan standar batas bawah:

5.2.4.1 Batas terjemahan

276 Implementasi harus dapat menerjemahkan dan menjalankan setidaknya satu program yang mengandung setidaknya satu instance dari setiap batasan berikut: [...]

279 - 12 pointer, array, dan deklarator fungsi (dalam kombinasi apa pun) yang memodifikasi tipe aritmatika, struktur, gabungan, atau batal dalam deklarasi

Batas atas adalah implementasi khusus.

PP
sumber
121
Standar C ++ "merekomendasikan" bahwa dukungan implementasi setidaknya 256. (Keterbacaan merekomendasikan agar Anda tidak melebihi 2 atau 3, dan bahkan kemudian: lebih dari satu harus luar biasa.)
James Kanze
22
Batas ini adalah tentang berapa banyak dalam satu deklarasi; itu tidak memaksakan batas atas pada berapa banyak tipuan yang dapat Anda capai melalui beberapa typedefs.
Kaz
11
@ Ka - ya, itu benar. Tetapi karena spec adalah (no pun intended) yang menetapkan batas bawah wajib, itu adalah batas atas yang efektif semua kompiler yang memenuhi spesifikasi diperlukan untuk mendukung. Tentu saja ini bisa lebih rendah daripada batas atas khusus vendor. Diulang secara berbeda (untuk menyelaraskannya dengan pertanyaan OP), ini adalah maksimum yang diizinkan oleh spesifikasi (apa pun akan menjadi spesifik vendor.) Sedikit menyinggung, programmer harus (setidaknya dalam kasus umum) memperlakukan itu sebagai milik mereka. batas atas (kecuali jika mereka memiliki alasan yang sah untuk mengandalkan batas atas khusus vendor) ... saya pikir.
luis.espinal
5
Pada catatan lain, saya akan mulai memotong sendiri jika saya harus bekerja dengan kode yang memiliki rantai dereferencing panjang-pantat (khususnya ketika dibumbui secara bebas di semua tempat .)
luis.espinal
11
@beryllium: Biasanya angka-angka ini berasal dari survei perangkat lunak pra-standardisasi. Dalam kasus ini mungkin mereka melihat program C umum dan kompiler C yang ada, dan menemukan setidaknya satu kompiler yang akan mengalami masalah dengan lebih dari 12 dan / atau tidak ada program yang rusak jika Anda membatasi ke 12.
155

Sebenarnya, program C umumnya menggunakan tipuan pointer tak terbatas. Satu atau dua level statis adalah umum. Tiga tipuan jarang terjadi. Tetapi tak terbatas sangat umum.

Peniruan penunjuk tak terbatas dicapai dengan bantuan struct, tentu saja, tidak dengan deklarator langsung, yang tidak mungkin dilakukan. Dan sebuah struct diperlukan agar Anda dapat memasukkan data lain dalam struktur ini pada level yang berbeda di mana ini dapat berakhir.

struct list { struct list *next; ... };

sekarang kamu bisa punya list->next->next->next->...->next. Ini benar-benar hanya beberapa indirections pointer: *(*(..(*(*(*list).next).next).next...).next).next. Dan pada .nextdasarnya adalah noop ketika itu adalah anggota pertama dari struktur, jadi kita bisa membayangkan ini sebagai ***..***ptr.

Sebenarnya tidak ada batasan untuk ini karena tautan dapat dilalui dengan loop daripada ekspresi raksasa seperti ini, dan terlebih lagi, struktur dapat dengan mudah dibuat melingkar.

Dengan demikian, dengan kata lain, daftar tertaut dapat menjadi contoh utama untuk menambahkan tingkat tipuan lain untuk menyelesaikan masalah, karena Anda melakukannya secara dinamis dengan setiap operasi push. :)

Kaz
sumber
48
Itu masalah yang sama sekali berbeda - sebuah struct yang berisi pointer ke struct lain sangat berbeda dari pointer-pointer. Int ***** adalah tipe yang berbeda dari int ****.
fluffy
12
Itu tidak "sangat" berbeda. Perbedaannya lembut. Ini lebih dekat ke sintaks daripada semantik. Penunjuk ke objek penunjuk, atau penunjuk ke objek struktur yang berisi penunjuk? Itu adalah hal yang sama. Mendapatkan ke elemen kesepuluh dari daftar adalah sepuluh tingkat mengatasi tipuan. (Tentu saja, kemampuan untuk mengekspresikan struktur yang tak terbatas tergantung pada tipe struct yang dapat menunjuk ke dirinya sendiri melalui tipe struct yang tidak lengkap sehingga list->nextdan list->next->nextmerupakan tipe yang sama; jika tidak, kita harus membangun tipe infinite.)
Kaz
34
Saya tidak menyadari bahwa nama Anda mengembang ketika saya menggunakan kata "lembut". Pengaruh bawah sadar? Tetapi saya yakin saya telah menggunakan kata ini dengan cara ini sebelumnya.
Kaz
3
Juga ingat bahwa dalam bahasa mesin, Anda bisa mengulangi sesuatu seperti LOAD R1, [R1]selama R1 adalah pointer yang valid di setiap langkah. Tidak ada tipe yang terlibat selain "kata yang menyimpan alamat". Ada atau tidaknya tipe yang dideklarasikan tidak menentukan tipuan dan berapa level yang dimilikinya.
Kaz
4
Tidak jika strukturnya melingkar. Jika R1memegang alamat lokasi yang menunjuk ke dirinya sendiri maka LOAD R1, [R1]dapat dieksekusi dalam loop tak terbatas.
Kaz
83

Secara teoretis:

Anda dapat memiliki banyak tingkat tipuan yang Anda inginkan.

Praktis:

Tentu saja, tidak ada yang mengkonsumsi memori yang tidak dapat ditentukan, akan ada keterbatasan karena sumber daya yang tersedia di lingkungan host. Jadi praktis ada batas maksimum untuk apa suatu implementasi dapat mendukung dan implementasi harus mendokumentasikannya dengan tepat. Jadi dalam semua artefak tersebut, standar tidak menentukan batas maksimum, tetapi menentukan batas bawah.

Inilah rujukannya:

C99 Standar 5.2.4.1 Batas terjemahan:

- 12 pointer, array, dan deklarator fungsi (dalam kombinasi apa pun) yang memodifikasi tipe aritmatika, struktur, gabungan, atau batal dalam deklarasi.

Ini menentukan batas bawah yang harus didukung oleh setiap implementasi . Perhatikan bahwa dalam catatan kaki standar lebih lanjut mengatakan:

18) Implementasi harus menghindari memaksakan batas terjemahan tetap bila memungkinkan.

Alok Simpan
sumber
16
tipuan tidak meluap tumpukan!
Basile Starynkevitch
1
Dikoreksi, saya merasa errie membaca & menjawab q sebagai batas parameter yang dilewatkan ke fungsi. Saya tidak tahu mengapa ?!
Alok Hemat
2
@basile - Saya berharap tumpukan-kedalaman menjadi masalah di parser. Banyak algoritma penguraian formal memiliki tumpukan sebagai komponen kunci. Kebanyakan kompiler C ++ mungkin menggunakan varian turunan rekursif, tetapi bahkan yang bergantung pada tumpukan prosesor (atau, secara pedas, pada bahasa yang bertindak seolah-olah ada tumpukan prosesor). Lebih banyak aturan tata bahasa yang bersarang berarti tumpukan yang lebih dalam.
Steve314
8
tipuan tidak meluap tumpukan! -> Tidak! stack parser bisa meluap. Bagaimana tumpukan itu berhubungan dengan tipuan pointer? Tumpukan Parser!
Pavan Manjunath
Jika *kelebihan beban untuk sejumlah kelas berturut-turut, dan masing-masing kelebihan mengembalikan objek jenis lain di baris, maka bisa ada stackoverflow untuk panggilan fungsi berantai seperti itu.
Nawaz
76

Seperti yang dikatakan orang, tidak ada batasan "dalam teori". Namun, karena ketertarikan saya menjalankan ini dengan g ++ 4.1.2, dan bekerja dengan ukuran hingga 20.000. Kompilasi cukup lambat, jadi saya tidak mencoba lebih tinggi. Jadi saya kira g ++ juga tidak menetapkan batas apa pun. (Coba pengaturan size = 10dan cari di ptr.cpp jika tidak segera jelas.)

g++ create.cpp -o create ; ./create > ptr.cpp ; g++ ptr.cpp -o ptr ; ./ptr

create.cpp

#include <iostream>

int main()
{
    const int size = 200;
    std::cout << "#include <iostream>\n\n";
    std::cout << "int main()\n{\n";
    std::cout << "    int i0 = " << size << ";";
    for (int i = 1; i < size; ++i)
    {
        std::cout << "    int ";
        for (int j = 0; j < i; ++j) std::cout << "*";
        std::cout << " i" << i << " = &i" << i-1 << ";\n";
    }
    std::cout << "    std::cout << ";
    for (int i = 1; i < size; ++i) std::cout << "*";
    std::cout << "i" << size-1 << " << \"\\n\";\n";
    std::cout << "    return 0;\n}\n";
    return 0;
}
BoBTFish
sumber
72
Saya tidak bisa mendapatkan lebih dari 98242 ketika saya mencobanya. (Saya melakukan skrip dengan Python, menggandakan jumlah *sampai saya mendapatkan satu yang gagal, dan yang sebelumnya berlalu; Saya kemudian melakukan pencarian biner pada interval itu untuk yang pertama yang gagal. Seluruh tes membutuhkan waktu kurang dari satu detik untuk menjalankan.)
James Kanze
63

Kedengarannya menyenangkan untuk diperiksa.

  • Visual Studio 2010 (pada Windows 7), Anda dapat memiliki level 1011 sebelum mendapatkan kesalahan ini:

    kesalahan fatal C1026: stack overflow parser, program terlalu kompleks

  • gcc (Ubuntu), 100k + *tanpa crash! Saya kira perangkat keras adalah batasannya di sini.

(diuji hanya dengan deklarasi variabel)

mihai
sumber
5
Memang, produksi untuk operator unary adalah rekursif benar, yang berarti bahwa parser pengurang-shift akan menggeser semua *node ke stack sebelum dapat melakukan pengurangan.
Kaz
28

Tidak ada batasan, periksa contoh di sini .

Jawabannya tergantung pada apa yang Anda maksud dengan "level pointers." Jika Anda maksudkan, "Berapa tingkat tipuan yang dapat Anda miliki dalam satu deklarasi?" jawabannya adalah "Setidaknya 12."

int i = 0;

int *ip01 = & i;

int **ip02 = & ip01;

int ***ip03 = & ip02;

int ****ip04 = & ip03;

int *****ip05 = & ip04;

int ******ip06 = & ip05;

int *******ip07 = & ip06;

int ********ip08 = & ip07;

int *********ip09 = & ip08;

int **********ip10 = & ip09;

int ***********ip11 = & ip10;

int ************ip12 = & ip11;

************ip12 = 1; /* i = 1 */

Jika Anda maksudkan, "Berapa banyak level pointer yang dapat Anda gunakan sebelum program sulit dibaca," itu masalah selera, tetapi ada batasnya. Memiliki dua tingkat tipuan (pointer ke pointer ke sesuatu) adalah umum. Lebih dari itu semakin sulit untuk dipikirkan dengan mudah; jangan lakukan itu kecuali jika alternatifnya akan lebih buruk.

Jika Anda maksudkan "Berapa banyak tingkat tipuan pointer yang dapat Anda miliki saat runtime," tidak ada batasan. Titik ini sangat penting untuk daftar bundar, di mana setiap titik menunjuk ke yang berikutnya. Program Anda dapat mengikuti petunjuk selamanya.

Nandkumar Tekale
sumber
7
Hampir pasti ada batasnya, karena kompiler harus melacak informasi dalam jumlah memori yang terbatas. ( g++dibatalkan dengan kesalahan internal pada 98242 pada mesin saya. Saya berharap bahwa batas aktual akan tergantung pada mesin dan beban. Saya juga tidak berharap ini menjadi masalah dalam kode nyata.)
James Kanze
2
Ya @ MatthieuM. : Saya baru saja mempertimbangkan secara teoritis :) Terima kasih James untuk menyelesaikan jawaban
Nandkumar Tekale
3
Nah, daftar tertaut sebenarnya bukan penunjuk ke penunjuk,
melainkan
1
@ Random832: Nand berkata 'Jika Anda berarti "Berapa banyak tingkat tipuan pointer yang dapat Anda miliki saat runtime,"' jadi dia secara eksplisit menghapus pembatasan hanya berbicara tentang pointer ke pointer (* n).
LarsH
1
Saya tidak mengerti maksud Anda: ' Tidak ada batasan, lihat contoh di sini. 'Contohnya adalah tidak ada bukti bahwa tidak ada batasan. Ini hanya membuktikan bahwa tipuan bintang 12 adalah mungkin. Tidak ada yang membuktikan apa pun circ_listcontoh mengenai pertanyaan OP: Fakta bahwa Anda dapat melintasi daftar pointer tidak menyiratkan bahwa kompiler dapat mengkompilasi tipuan bintang n.
Alberto
24

Ini sebenarnya lebih lucu dengan pointer ke fungsi.

#include <cstdio>

typedef void (*FuncType)();

static void Print() { std::printf("%s", "Hello, World!\n"); }

int main() {
  FuncType const ft = &Print;
  ft();
  (*ft)();
  (**ft)();
  /* ... */
}

Seperti diilustrasikan di sini, ini memberi:

Halo Dunia!
Halo Dunia!
Halo Dunia!

Dan itu tidak melibatkan overhead runtime, jadi Anda mungkin dapat menumpuknya sebanyak yang Anda inginkan ... sampai kompiler Anda tersedak file.

Matthieu M.
sumber
20

Tidak ada batasan . Pointer adalah sepotong memori yang isinya adalah alamat.
Seperti yang Anda katakan

int a = 10;
int *p = &a;

Penunjuk ke penunjuk juga merupakan variabel yang berisi alamat penunjuk lain.

int **q = &p;

Berikut qini adalah pointer ke pointer memegang alamat pyang sudah memegang alamat a.

Tidak ada yang khusus tentang pointer ke pointer.
Jadi tidak ada batasan pada rantai ponitor yang memegang alamat pointer lain.
yaitu.

 int **************************************************************************z;

Diperbolehkan.

Sachin Mhetre
sumber
17

Setiap pengembang C ++ seharusnya sudah mendengar tentang programmer Bintang Tiga yang terkenal

Dan tampaknya benar-benar ada "penghalang penunjuk" ajaib yang harus disamarkan

Kutipan dari C2:

Pemrogram Tiga Bintang

Sistem penilaian untuk programmer-C. Semakin banyak pointer tidak langsung Anda (yaitu semakin "*" sebelum variabel Anda), semakin tinggi reputasi Anda. Programer C-bintang tidak ada secara virtual, karena hampir semua program non-sepele memerlukan pointer. Sebagian besar adalah programmer satu bintang. Di masa lalu (yah, saya masih muda, jadi ini terlihat seperti masa lalu bagi saya setidaknya), orang kadang-kadang akan menemukan sepotong kode yang dilakukan oleh seorang programmer bintang tiga dan menggigil dengan kagum. Beberapa orang bahkan mengklaim telah melihat kode bintang tiga dengan fungsi pointer yang terlibat, pada lebih dari satu tingkat tipuan. Terdengar senyata UFO bagiku.

Mare Infinitus
sumber
2
github.com/psi4/psi4public/blob/master/src/lib/libdpd/… dan sejenisnya ditulis oleh seorang programmer bintang 4. Dia juga teman saya dan, jika Anda cukup membaca kode, Anda akan memahami alasan mengapa itu layak untuk 4 bintang.
Jeff
13

Perhatikan bahwa ada dua pertanyaan yang mungkin di sini: berapa banyak tingkat tipuan pointer yang dapat kita capai dalam tipe C, dan berapa banyak tingkat tipuan pointer yang dapat kita masukkan ke dalam satu deklarator tunggal.

Standar C memungkinkan maksimum untuk dikenakan pada yang pertama (dan memberikan nilai minimum untuk itu). Tapi itu bisa dielakkan melalui beberapa deklarasi typedef:

typedef int *type0;
typedef type0 *type1;
typedef type1 *type2; /* etc */

Jadi pada akhirnya, ini adalah masalah implementasi yang terhubung dengan gagasan tentang seberapa besar / kompleks sebuah program C dapat dibuat sebelum ditolak, yang merupakan kompiler yang sangat spesifik.

Kaz
sumber
4

Saya ingin menunjukkan bahwa memproduksi suatu tipe dengan jumlah * yang sewenang-wenang adalah sesuatu yang dapat terjadi dengan metaprogramming template. Saya lupa persis apa yang saya lakukan, tetapi disarankan agar saya dapat menghasilkan tipe berbeda baru yang memiliki beberapa jenis meta manuver di antara mereka dengan menggunakan tipe T * rekursif .

Templat Metaprogramming adalah keturunan yang lambat menuju kegilaan, jadi tidak perlu membuat alasan ketika membuat suatu tipe dengan beberapa ribu level tipuan. Ini hanya cara praktis untuk memetakan bilangan bulat kacang polong, misalnya, ke ekspansi templat sebagai bahasa fungsional.

JDługosz
sumber
Saya akui saya tidak memahami jawaban Anda sepenuhnya, tetapi itu memberi saya area baru untuk dijelajahi. :)
ankush981
3

Peraturan 17.5 dari standar MISRA C 2004 melarang lebih dari 2 level tipuan pointer.

Kostmo
sumber
15
Cukup yakin itu adalah rekomendasi untuk programmer, bukan untuk kompiler.
Cole Johnson
3
Saya membaca dokumen dengan aturan 17.5 tentang lebih dari 2 level tipuan pointer. Dan itu tidak serta merta melarang lebih dari 2 level. Ini menyatakan bahwa putusan tersebut harus diikuti karena lebih dari 2 tingkat adalah "non-compliant"standar mereka. Kata atau frasa penting dalam keputusan mereka adalah penggunaan kata "should"dari pernyataan ini: Use of more than 2 levels of indirection can seriously impair the ability to understand the behavior of the code, and should therefore be avoided.Ini adalah pedoman yang ditetapkan oleh organisasi ini sebagai lawan dari aturan yang ditetapkan oleh standar bahasa.
Francis Cugler
1

Tidak ada yang namanya batas nyata tetapi ada batas. Semua pointer adalah variabel yang biasanya disimpan dalam stack, bukan heap . Tumpukan biasanya kecil (dimungkinkan untuk mengubah ukurannya selama beberapa penautan). Jadi katakanlah Anda memiliki tumpukan 4MB, apa ukuran yang cukup normal. Dan katakanlah kita memiliki pointer yang berukuran 4 byte (ukuran pointer tidak sama tergantung pada pengaturan arsitektur, target dan kompiler).

Pada kasus ini 4 MB / 4 b = 1024 jumlah maksimum yang memungkinkan adalah 1048576, tetapi kita tidak boleh mengabaikan fakta bahwa beberapa hal lain ada di tumpukan.

Namun beberapa kompiler mungkin memiliki jumlah maksimum rantai penunjuk, tetapi batasnya adalah ukuran tumpukan. Jadi, jika Anda meningkatkan ukuran tumpukan selama menghubungkan dengan infinity dan memiliki mesin dengan memori infinity yang menjalankan OS yang menangani memori itu sehingga Anda akan memiliki rantai penunjuk yang tidak terbatas.

Jika Anda menggunakan int *ptr = new int;dan meletakkan pointer Anda ke tumpukan, itu tidak batas cara biasa akan menjadi ukuran tumpukan, bukan tumpukan.

EDIT Sadarilah itu infinity / 2 = infinity. Jika mesin memiliki lebih banyak memori maka ukuran pointer bertambah. Jadi jika memori tak terhingga dan ukuran penunjuk adalah tak terhingga, maka itu adalah berita buruk ... :)

ST3
sumber
4
A) Pointer dapat disimpan di heap ( new int*). B) An int*dan int**********memiliki ukuran yang sama, setidaknya pada arsitektur yang masuk akal.
@foldfold A) Pointer ya dapat disimpan dalam tumpukan Tetapi itu akan menjadi hal yang sangat berbeda seperti membuat wadah yang menampung pointer apa yang menunjuk ke pointer sebelumnya berikutnya. B) Tentu saja int*dan int**********memiliki ukuran yang sama, saya tidak mengatakan bahwa mereka memiliki perbedaan.
ST3
2
Maka saya tidak melihat bagaimana ukuran stack bahkan relevan.
@ rightfold Saya sudah memikirkan cara distribusi data yang biasa ketika semua data ada di tumpukan dan di stack itu hanya petunjuk ke data itu. Ini akan menjadi cara yang biasa , tetapi saya setuju bahwa adalah mungkin untuk meletakkan pointer di stack.
ST3
"Tentu saja int * dan int ********** memiliki ukuran yang sama" - standar tidak menjamin itu (walaupun saya tahu tidak ada platform di mana itu tidak benar).
Martin Bonner mendukung Monica
0

Itu tergantung pada tempat Anda menyimpan pointer. Jika mereka berada di tumpukan Anda memiliki batas yang cukup rendah . Jika Anda menyimpannya di tumpukan, batas Anda jauh lebih tinggi.

Lihatlah program ini:

#include <iostream>

const int CBlockSize = 1048576;

int main() 
{
    int number = 0;
    int** ptr = new int*[CBlockSize];

    ptr[0] = &number;

    for (int i = 1; i < CBlockSize; ++i)
        ptr[i] = reinterpret_cast<int *> (&ptr[i - 1]);

    for (int i = CBlockSize-1; i >= 0; --i)
        std::cout << i << " " << (int)ptr[i] << "->" << *ptr[i] << std::endl;

    return 0;
}

Ini menciptakan pointer 1M dan pada titik menunjukkan ke apa mudah untuk melihat apa rantai pergi ke variabel pertama number.

BTW. Ini menggunakan 92KRAM jadi bayangkan seberapa dalam Anda bisa melangkah.

ST3
sumber