Archiv für den Monat: Juli 2010

Signature-Programme

Eine Signatur im Usenet soll maximal 4 Zeilen lang und je Zeile maximal 80 Zeichen umfassen. Statt aber nur langweilig seinen Namen/Anschrift/Adresse zu nennen, kann man diesen Platz auch dazu nutzen, um mehr oder weniger sinnfreie Programme dort zu hinterlegen.

Klassischer Weise wurden die meisten Signatur-Programme in der Programmiersprache C geschrieben, da diese für viele der sonst nur (platz)aufwendig beschreibbaren Strukturen auch kürzere Varianten möglich waren. Wer zudem noch auf die korrekten Reihenfolge der Interpretierung achtet, kann hier enorm viel Quellcode-Speicherplatz sparen.

Durch das Ausreizen der Fähigkeiten der Programmiersprache werden solche Programme auch häufig recht schwierig zu lesen oder zu verstehen, man bezeichnet solche Programme als Obfuscated.

Im laufe der Zeit habe ich hauptsächlich zwei Signature-Programs genutzt, beide sind in C geschrieben und zumindest auf einer Unix/Linux-Konsole mit einem GNU C-Compiler (gcc) compilierbar und anschliessend ausführbar.

Einfache verschleierte Ausgabe einer URL

main(a,b){char*c="ji3yijyxwjlfr44?uyymm";for(;a++<21;)putchar(c[21-a]-5);}

Zugegeben, das war nicht unbedingt ein sehr schwieriges Programm, aber es verschleierte zumindest ein wenig seine Herkunft. Sicherlich sieht man sofort, dass dies eindeutig das ältere Programm ist. 🙂
Vertikaler geschwungener Scroller der Initialien

main(a,b){int c=0,d=0,e[10]={70,137,145,98,0,255,4,8,4,255};for(;++d;c=0){puts
("\e[2J");while(c<10){for(b=a=abs((c+++d)%16-8)+1;--a>1/(10-b)-1/b;)putchar(32)
;for(a=8;a>0;putchar((e[c-1]>>--a&1)*3+32));puts("");}usleep(34567);}}

Wie man sieht konnte ich mich auch mit drei Zeilen begnügen. Es gibt auch noch Stellen im Programm, welche weiter optimierbar wären, aber da ich sowieso nicht mehr soviel im Usenet aktiv bin, fehlt mir auch der Reiz.

Da ich öfters darauf angesprochen wurde, erkläre ich gerne die Funktionsweise en detail. Etwas klarer wird das Programm, wenn man es zunächst einmal formatiert darstellt. Ich habe bereits für die einzelnen Blöcke ein grobe Erklärung als Kommentar hinzugefügt:

main(a,b)
{
  /* Declaration of variables and data structure (the Initials) */
  int c=0,d=0,e[10]={70,137,145,98,0,255,4,8,4,255};
  /* Endless for, initializes c to 0 and increases "d" everytime */
  for(;++d;c=0)
  {
    /* Clear screen */
    puts("\e[2J");
    /* Ten lines to show (according to 10 data items in e[] */
    while(c<10) {   /* How many spaces are needed in front of each data item */
      for(b=a=abs((c+++d)%16-8)+1;--a>1/(10-b)-1/b;)
        putchar(32);
      /* Print the field according to the data with stars or spaces */
      for(a=8;a>0;putchar((e[c-1]>>--a&1)*3+32));
      /* Newline */
      puts("");
    }
  usleep(34567);
  }
}

Das besondere liegt offensichtlich in den for()-Schleifen, daher ist es sinnvoll, sich diese noch einmal etwas genauer zu betrachten. Eine for()-Schleife besteht aus drei Teilen, der Initialisierung, der Abbruchbedingung und der Schrittweite. Das besondere ist, dass jeder dieser Teile auch ganz „regulären“ Code ausführen kann. Die Initialisierung wird nur einmalig ausgeführt, die Bedingung und die Schrittweite bei jedem Durchlauf. Hier also zunächst die erste Schleife:

for(
  b=a=abs((c+++d)%16-8)+1;
  --a>1/(10-b)-1/b;
)

Initial werden b und a auf das Ergebnis aus

abs((c+++d)%16-8)+1

gesetzt. c gibt dabei die aktuelle Zeile wieder, d verändert sich erst nach jedem vollständigen Bild, wird also erst nach dem nächsten „Clear screen“ wieder verändert. Die Teilanweisung

c+++d

erhöht dabei erst das c und führt anschliessend die Addition aus. Die längere Schreibweise dafür wäre:

c = c + 1;
c + d;

Der Rückgabewert dieser Funktion wird nun modulo 16 gerechnet, somit erhalten wir einen Wert zwischen 0 und 15. Durch das Abziehen der 8 (ergibt -8 bis 7) und das anschliessende Verwenden der Funktion

abs()

erhält man einen Wert zwischen 0 und 8, das +1 verschiebt das wieder auf den Bereich 1 und 9.

Durch die Initialisierung haben nun beide Variablen denselben Wert zwischen 1 und 8. Der Bedingungsteil wird leichter verständlich, wenn man sich die Formel einfach etwas getrennter anschaut:

--a > 1/(10-b) - 1/b

Die Prüfung lautet also: „Ist (das zuvor verkleinerte) a noch grösser als die Subtraktion der Brüche? Wenn ja, dann noch eine Schleife durchlaufen. Da in dem „Schrittweite“-Bereich die Variable a immer um eins reduziert wird, ist diese Bedingung irgendwann nicht mehr korrekt. Das ganze dient einfach dazu, die hohen und die kleinen Werte von a ein wenig zu dämpfen. Nimmt b die Werte 1 oder 2 an, wird die Schleife immer zweimal durchlaufen, bei b=8 oder b=9 immer achtmal. Auf diese Weise sieht die Animation etwas „flüssiger“ aus, da es mehr an die Sinus-Kurve erinnert, ohne dass ein Sinus gerechnet werden muss.

Bei jedem Durchlauf dieser Schleife wird ein Leerzeichen ausgegeben.

Die zweite for()-Schleife hat Ihren besondern Reiz in dem „Schrittweite“-Part:

for(
  a=8;
  a>0;
  putchar((e[c-1]>>--a&1)*3+32)
);

Die Schleife soll offensichtlich achtmal durchlaufen werden, normalerweise würde man einfach ein a– als Schrittweite-Part nehmen. Durch das Voranstellen der Minuszeichen vor dem a wird das dekrementieren bereits vor der Auswertung durchgeführt.

Der Teil

e[c-1]

greift banal auf das Datenfeld zu, welches für die cte Zeile zuständig ist. Das -1 wird gebraucht, weil bei Arrays in c das erste Element mit 0 indexiert wird.

Der ausgelesene Werte aus dem Array wird nun mittels >> geshiftet. Der Wert in dem Datenfeld ist ein Wert zwischen 0 und 255, lässt sich also in 8 Bit darstellen. Der erste Wert „70“ wird in der Binärschreibweise zu: 01000110. Wir verschieben („shiften“) nun diesen Bitvektor um a-1 nach „rechts“. a durchläuft in unserer Schleife die Werte 8 bis 1, also shiften wir zunächst um 7:

Shifting bei a=7 und Bitvektor 01000110:

01000110 (0)
x0100011 (1)
xx010001 (2)
xxx01000 (3)
xxxx0100 (4)
xxxxx010 (5)
xxxxxx01 (6)
xxxxxxx0 (7)

Von links wird tatsächlich immer eine „0“ nachgeschoben, durch das „x“ versuche ich nur zu verdeutlichen, dass man sich einfach nur das höchste Bit geholt hat. Es ist eine 0. Der Rest des Schrittweite-Parts wertet nun diesen gewonnenen Wert aus:

(0 & 1) * 3 + 32

0 UND 1 (als Bitvergleich gesehen) ergibt „Falsch“, also eine 0.
0 multipliziert mit 3 ergibt 0, plus 32 eine 32.
Und diese 32 wird nun als putchar(32); ausgegeben – ein Leerzeichen.

Bei a=6 resultiert aus dem Bitvektor 01000110 allerdings:

xxxxxx01

01 UND 1 ergibt ein „Wahr“, also eine 1. (Wir prüfen mit diesem Bitvergleich offensichtlich nur den kleinsten Bitteil)
1 multipliziert mit 3 ergibt 3, plus 32 eine 35.
putchar(35) liefert – richtig – das Zeichen „#“.

Die zweite for()-Schleife hat keinen Ausführungsteil und durchläuft sich daher einfach nur selbst. (Sie hat ja auch schon genug zu tun…)

Insgesamt kann man also sehen, dass dieses kleine Programm die eine oder andere Programmierkenntnis schon erfordert:

  • Steuerzeichencodes der Shell: puts(„\e[2J“);
  • Relevanz der Reihenfolge von Evaluierung und Berechnung (a– und –a)
  • Shifting von Werten
  • Prioritäten von Anweisung, zur Vermeidung von unnötigen Klammern: c+++d
  • Lustige „Simulation“ komplexer mathematischer Funktionen wie sin()

Da man in einem C-long 32 Bit speichern kann, lässt sich das Programm mit ein paar kleinen Modifikationen auch dazu nutzen, z.B. meinen Vornamen in horizontaler Schreibweise darzustellen. Allerdings ist es dann nicht mehr so kompakt. 🙂

main(a){long long b,c,d=0,f;char*e[9]={"QINgW1","AU@4A2","9U@412","9mL611","9UH"
"6Q0","9U@4A0","5U@4A2","3U@7Q1"};for(;++d;c=0){puts("\e[2J");for(;c<8;f=1){for
(a=b=0;a<7;f*=64)b+=(e[c][a++]-48)*f;for(a=(a=abs((c+++d)%16-8))-(a>>3);a>1;
printf(" ",a--));for(a=64;a>0;putchar((b>>--a/2&1)*3+32));puts("");}usleep(34567);}}

Mithilfe von atoi(), also des Wandeln von „ASCII-Zeichen zu Integer“ kann man noch eine deutlich kompaktere Darstellungsform finden, auch für das ursprüngliche Programm – allerdings muss man dann prüfen, ob durch die zusätzliche Verwendung von atoi() und die evtl. notwendigen zusätzlichen Rechenoperationen zuviel Byte verschwendet werden und entsprechend die „Optimierung“ zu einer Deoptimierung wird… 🙂