Döngüler veya koşullu ifadeler olmadan 1'den 1000'e kadar yazdırılan C kodu nasıl çalışır?


148

Döngüler veya koşulsuz 1'den 1000'e kadar yazdırılanC kod buldum : Ama nasıl çalıştığını anlamıyorum. Herkes kodu geçip her satırı açıklayabilir?

#include <stdio.h>
#include <stdlib.h>

void main(int j) {
  printf("%d\n", j);
  (&main + (&exit - &main)*(j/1000))(j+1);
}

1
C olarak mı yoksa C ++ olarak mı derliyorsunuz? Hangi hataları görüyorsunuz? mainC ++ ile çağrı yapamazsınız .
ninjalj

@ninjalj Bir C ++ projesi oluşturdum ve kodun kopyalanması / geçmesi hata: yasadışı, sol işlenen 'void (__cdecl *) (int)' türüne sahip ve ifade tam bir nesne türüne işaretçi olmalıdır
ob_dev

1
@ninjalj Bu kod ideone.org üzerinde çalışıyor ancak visual studio üzerinde değil ideone.com/MtJ1M
ob_dev

@oussama Benzer, ancak anlaşılması biraz daha zor: ideone.com/2ItXm Bir şey değil. :)
Mark

2
bu satırdaki tüm '&' karakterlerini kaldırdım (& main + (& exit - & main) * (j / 1000)) (j + 1); ve bu kod hala çalışıyor.
ob_dev

Yanıtlar:


264

Hiç böyle kod yazmayın.


Çünkü j<1000, j/1000sıfırdır (tamsayı bölümü). Yani:

(&main + (&exit - &main)*(j/1000))(j+1);

şuna eşittir:

(&main + (&exit - &main)*0)(j+1);

Hangisi:

(&main)(j+1);

Hangi çağrıları mainile j+1.

Eğer j == 1000, o zaman aynı çizgiler şu şekilde ortaya çıkar:

(&main + (&exit - &main)*1)(j+1);

Hangi aşağı kaynar

(&exit)(j+1);

Hangi exit(j+1)programdan ayrılır.


(&exit)(j+1)ve exit(j+1)aslında aynı şeydir - C99 §6.3.2.1 / 4'ten alıntı:

İşlev belirleyici, işlev türüne sahip bir ifadedir. Sizeof operatörünün veya unary & operatörünün işleneni dışında, " işlev döndüren tür " türüne sahip bir işlev belirleyici " işlev döndüren tür" işaretçisine sahip bir ifadeye dönüştürülür .

exitbir fonksiyon göstergesidir. &Operatörün tek adresi olmadan bile , işlev gösterecek bir işaretçi olarak kabul edilir. ( &Sadece açık yapar.)

Ve işlev çağrıları §6.5.2.2 / 1 ve aşağıda açıklanmıştır:

Aranan işlevi ifade eden ifade, void döndüren veya dizi türü dışında bir nesne türü döndüren işlev için tür işaretçisine sahip olmalıdır .

Bu nedenle exit(j+1), işlev türünün bir işaretçi-işlev türüne otomatik dönüşümü nedeniyle çalışır ve işaretçi-işlev türüne (&exit)(j+1)açık bir dönüştürme ile de çalışır.

Bununla birlikte, yukarıdaki kod uygun değildir ( mainya iki argüman alır ya da hiç &exit - &mainalmaz) ve bence §6.5.6 / 9'a göre tanımlanmamıştır:

İki işaretçi çıkarıldığında, her ikisi de aynı dizi nesnesinin öğelerini veya dizi nesnesinin son öğesinin ötesini işaret eder; ...

Ekleme (&main + ...)kendi içinde geçerli olacaktır ve, kullanılabilir , eğer miktar sıfırdı eklendi §6.5.6 / 7 demesinden itibaren

Bu işleçlerin amaçları doğrultusunda, bir dizinin bir öğesi olmayan bir nesneye işaretçi, bir uzunluk dizisinin birinci elemanına bir öğe işaretçisi olan nesnenin tipi ile bir işaretçi gibi davranır.

Yani sıfır eklemek &maintamam olurdu (ama çok kullanmak değil).


4
foo(arg)ve (&foo)(arg)eşdeğerdir, arg argümanıyla foo derler. newty.de/fpt/fpt.html , işlev işaretçileri hakkında ilginç bir sayfadır.
Mat

1
@Krishnabhadra: ilk durumda, foobir işaretçi, &fooo işaretçinin adresidir. İkinci durumda, foobir dizidir ve &foofoo'ya eşdeğerdir.
Mat

8
Gereksiz derecede karmaşık, en azından C99 için:((void(*[])()){main, exit})[j / 1000](j + 1);
Per Johansson

1
&foofoodiziye gelince aynı değildir . &foodiziye foobir işaretçi, ilk öğeye bir işaretçi. Yine de aynı değere sahipler. Fonksiyonlar için funve &funher ikisi de işleve işaret eder.
Per Johansson

1
FYI, yukarıda değinilen diğer sorunun cevabına bakarsanız, aslında rahat C99 olan bir varyasyon olduğunu göreceksiniz. Korkutucu, ama gerçek.
Daniel Pryden

41

Özyineleme, işaretçi aritmetiği kullanır ve tamsayı bölümünün yuvarlama davranışından yararlanır.

j/1000Herkes için 0 olarak aşağı vadeli mermi j < 1000; bir kere julaşır 1000, bu, 1 ila değerlendirir.

Şimdi varsa a + (b - a) * n, n0 veya 1 aise n == 0, bif ve if ile sonuçlanırsınız n == 1. Kullanımı &main(adres main()) ve &exitiçin ave bterimi (&main + (&exit - &main) * (j/1000))döner , 1000'in altında olan , aksi takdirde. Sonuçta ortaya çıkan işlev işaretçisi argümanla beslenir .&mainj&exitj+1

Bu bütün yapı tekrarlayan davranışla sonuçlanır: j1000'in altındayken mainkendini tekrarlamalı olarak çağırır; ne zaman j1000 ulaşır, bu aramaları exitçıkış kodu 1001 ile programı bir çıkış yapma yerine (tür kirli ait olmakla işleri).


1
İyi cevap, ama bir şüphe .. 1001 çıkış kodu ile nasıl ana çıkış? Main bir şey döndürmüyor .. Herhangi bir varsayılan dönüş değeri var mı?
Krishnabhadra

2
J 1000'e ulaştığında, ana artık kendi içine çekilmez; bunun yerine, exitçıkış kodunu bağımsız değişkeni olarak alan ve geçerli işlemden çıkan libc işlevini çağırır . Bu noktada, j 1000'dir, bu nedenle j + 1, çıkış kodu haline gelen 1001'e eşittir.
tdammers
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.