Forum: PC-Programmierung Zufallsfunktion liefert bei wiederholtem Aufruf den gleichen Wert


von gox (Gast)


Lesenswert?

Hallo,

ich bin Student der Elektrotechnik und beschäftige mich zur Zeit während 
meiner Semesterferien mit C-Programmierung. Jetzt bin ich auf ein 
Problem gestoßen, mit dem ich überhaupt nicht klar komme - ich hoffe Ihr 
könnt mir helfen.


Folgende Funktion first erzeugt ein array[4]. Jedes element des array 
enthält eine Zufallszahl von 0-5. Es darf keine der Zahlen 0-5 in mehr 
als einem Arrayelement stehen.
1
#include <time.h>
2
void first(int* begin[4])
3
{
4
    srand(time(0));
5
6
    begin[0]=rand()%6;
7
8
    do
9
    {
10
        begin[1]=rand()%6;
11
    }
12
    while(begin[1]==begin[0]);
13
14
    do
15
    {
16
        begin[2]=rand()%6;
17
    }
18
    while((begin[2]==begin[0])||(begin[2]==begin[1]));
19
20
    do
21
    {
22
        begin[3]=rand()%6;
23
    }
24
    while((begin[3]==begin[2])||(begin[3]==begin[0])||(begin[3]==begin[1]));
25
}



Diese Funktion rufe ich in der main 2 mal auf und gebe das array danach 
jeweils testweise aus.
Das Problem ist, dass 2 mal das gleiche Array ausgegeben wird. Warum ist 
das so? Eigentlich müsste doch bei jedem Funktionsaufruf neu "gewürfelt" 
werden. Bzw. was muss ich ändern, dass es so ist? Danke schonmal für 
Eure Hilfe!
1
void main()
2
{
3
    int testar[4];
4
    first(&testar);
5
    printf("%d   %d   %d   %d\n",testar[3],testar[2],testar[1],testar[0]);//Testzeile
6
    first(&testar);
7
    printf("%d   %d   %d   %d\n",testar[3],testar[2],testar[1],testar[0]);//Testzeile
8
9
10
11
}

von Marcus B. (raketenfred)


Lesenswert?

Debug mal die rand ausgaben

und time(0), liefert die immer wieder was neues?

von Volker Z. (vza)


Lesenswert?

Das könnte am fehlenden C-Buch liegen.

Du übergibst ein Array von Zeigen auf int.
Du benutzt aber ein Array von int.

ändre mal
1
void first(int* begin)
und
1
    first(testar);

von Karl H. (kbuchegg)


Lesenswert?

gox schrieb:

> Diese Funktion rufe ich in der main 2 mal auf und gebe das array danach
> jeweils testweise aus.
> Das Problem ist, dass 2 mal das gleiche Array ausgegeben wird. Warum ist
> das so?

Dein Problem ist, dass der Rechner zu schnell ist. Dein time(0) ändert 
sich zwischen den Aufrufen nicht. Damit startet der 
Zufallszahlengenerator immer mit der gleichen Zahl und liefert dann auch 
immer die gleiche Folge.


das srand stellt man sinnigerweise ganz an den Anfang in main() und ruft 
es auch nur ein einziges mal auf. Das ist sogar sehr wichtig, dass man 
diesen Aufruf nur einmal macht. Denn Zufallszahlen definieren sich durch 
statistische Eigenschaften. Die kann ein Generator wie rand() aber nur 
dann aufbauen, wenn du ihn in Ruhe arbeiten lässt. Jedesmal wenn du 
srand() einsetzt, lässt du den Generator nicht mehr arbeiten, sondern 
zwingst ihm etwas auf - deine statistische Verteilung der Zahlen ist 
hinüber und die Zahlen sind nicht mehr pseudo-zufällig, sondern es gibt 
dubiose statistische Zusammenhänge zwischen den einzelnen Zufallsreihen.

Und deine Arraybehandlung. Lies noch mal nach, wie Argument passing in C 
funktioniert. Speziell bei Arrays. Da ist die Sache nämlich ein wenig 
anders als bei normalen Variablen.



Edit: zu langsam

von gox (Gast)


Lesenswert?

Danke für eure Antworten,

@Marcus B.: ich hab mir den Wert von time(0) mal ausgeben lassen, indem 
ich nach
1
srand(time(0));
mal time(0) mit printf ausgegeben habe
Das Resultat ist, dass es tatsächlich in beiden Durchläufen gleich ist.
Das Ausgeben von srand(time(0)) bringt das gleiche Ergebnis.


@Volker Zabe: Ich hab hier das Buch "C-Programmierung unter Linux, UNIX 
und Windows" von den Autoren Herold und Arndt.
An der Übergabe des Arrays liegt das Problem nicht, weil ich mir das 
Array auch testweise schonmal innerhalb der Funktion und außerhalb der 
funktion ausgeben hab lassen, und hab das gleiche rausbekommen.

@ Karl Heinz Buchegger: Ich habs gleich mal probiert und es funktioniert 
mit dem srand() in der main()!

Seid ihr sicher, dass ich das array falsch übergebe? Das Programm tut 
jetzt nämlich was es soll. Oder ist das unsauberer Stil?

von gox (Gast)


Lesenswert?

@Volker Zabe: Ich hab die von dir vorgeschlagenen Änderungen mal 
ausprobiert. Aber dann meckert CodeBlocks beim Kompilieren immer schon 
rum und das Programm wird nicht ausgeführt.

von DirkB (Gast)


Lesenswert?

WAS meckert der Compiler?

von Volker Z. (vza)


Lesenswert?

gox schrieb:
> Ich hab die von dir vorgeschlagenen Änderungen mal
> ausprobiert. Aber dann meckert CodeBlocks beim Kompilieren immer schon
> rum und das Programm wird nicht ausgeführt.

Bei mir (MSC) ist es umgekehrt.

Deine Version:  6 Fehler
C:\projects_svn\winkel\test_atan\test_atan.cpp(146) : error C2440: '=' : 
cannot convert from 'int' to 'int *'
        Conversion from integral type to pointer type requires 
reinterpret_cast, C-style cast or function-style cast
C:\projects_svn\winkel\test_atan\test_atan.cpp(157) : error C2664: 
'first' : cannot convert parameter 1 from 'int (*)[4]' to 'int *[]'
        Types pointed to are unrelated; conversion requires 
reinterpret_cast, C-style cast or function-style cast

Änderung     :  0 Fehler, 0 Warnings

ciao Volker

von Karl H. (kbuchegg)


Lesenswert?

gox schrieb:
^
> Seid ihr sicher, dass ich das array falsch übergebe?


Ja, sind wir.

> Das Programm tut
> jetzt nämlich was es soll. Oder ist das unsauberer Stil?

Genau genommen ist es falsches C.
Schon alleine deswegen, weil deine Funktion einen Pointer auf ein Array 
bekommt, welches aus lauter Pointern besteht.
Was dich rettet ist die Tatsache, dass auf deinem System offenbar 
sizeof(int) == sizeof( void*)
Und natürlich, dass der Compiler einen & bei einem Array-Argument 
ignoriert. Das entspricht zwar nicht den C-Vorgaben, manche Compiler tun 
das aber trotzdem.

von Martin (Gast)


Lesenswert?

Das das trotzdem funktioniert  ist reines Glueck.


int * begin[4]    ist ein  [4]-fach-Array  von Pointern auf ein int 
array



wenn Du schreibst

if( begin[1]==begin[0])  blah ..

vergleichst Du zwei Pointer , nicht zwei Integer.
Nun ist witzigerweise  beides das Gleiche, also funktioniert das 
Programm.

Lass  einfach  den  *  und das  &  weg, und alles ist gut.

von Karl H. (kbuchegg)


Lesenswert?

Richtig ist es so
1
#include <stdio.h>
2
#include <stdlib.h>
3
#include <time.h>
4
5
void first(int* begin)
6
{
7
8
    begin[0]=rand()%6;
9
10
    do
11
    {
12
        begin[1]=rand()%6;
13
    }
14
    while(begin[1]==begin[0]);
15
16
    do
17
    {
18
        begin[2]=rand()%6;
19
    }
20
    while((begin[2]==begin[0])||(begin[2]==begin[1]));
21
22
    do
23
    {
24
        begin[3]=rand()%6;
25
    }
26
    while((begin[3]==begin[2])||(begin[3]==begin[0])||(begin[3]==begin[1]));
27
}
28
 
29
int main()
30
{
31
    int testar[4];
32
33
    srand(time(0));
34
35
    first(testar);
36
    printf("%d   %d   %d   %d\n",testar[3],testar[2],testar[1],testar[0]);//Testzeile
37
38
    first(testar);
39
    printf("%d   %d   %d   %d\n",testar[3],testar[2],testar[1],testar[0]);//Testzeile
40
}

Die eckigen Klammern bei der Funktionsdefinition sind nur syntaktischer 
Zucker. Ob du das so schreibst
1
void first(int* begin)
oder so
1
void first(int begin[])
ist beides dasselbe (die 4 da drinnen spielen sowieso keine Rolle). In 
beiden Fällen wird der Funktion die Startadresse des Arrays übergeben. 
Im zweiten Fall sieht man vielleicht noch etwas eher, dass der 
Programmierer der Funktion mit einem Array rechnet, aber aus C-Sicht 
sind es nur unterschiedliche Schreibweisen desselben Sachverhaltes.

Und beim Funktionsaufruf

   first( testar );

wird der & weggelassen, weil ein Arrayname für sich alleine (also ohne 
Index, wie bei testar[0]) an dieser Stelle sowieso automatisch für die 
Startadresse des Arrays steht. Ein Arrayname alleine IST gleichbedeutend 
mit der Startadresse eines Arrays. Dies folgt aus der Art und Weise, wie 
der Indexoperator in C definiert ist. Der ist nämlich auch nur eine 
andere Schreibweise für etwas anderes :-)

      array[i]   <==>   *(array + i)

und hier steht der Name des Arrays wieder alleine und fungiert somit als 
Startadresse der Daten im Speicher. Zu dieser Startadresse wird dann 
noch der Offset zum i-ten Element dazugezählt. Das Ergebnis ist dann 
wieder eine Adresse, nämlich die des i-ten Elements und der * 
schlussendlich ist dann dafür zuständig die Daten von dort zu lesen oder 
zu schreiben.

von gox (Gast)


Lesenswert?

Danke für die Mühe und auch die Erklärungen unten finde ich echt klasse 
und gut verständlich.
Bei mir funktionierts nur mit
1
void first(int begin[])
ich darf die [] also nicht weglassen.
andernfalls meldet mir der compiler "error: conflicting types of 
'first'" und markiert die eben zitierte zeile

von Karl H. (kbuchegg)


Lesenswert?

gox schrieb:
> Danke für die Mühe und auch die Erklärungen unten finde ich echt klasse
> und gut verständlich.
> Bei mir funktionierts nur mit
1
void first(int begin[])
> ich darf die [] also nicht weglassen.

Die [] darfst du natürlich nicht einfach weglassen. Wenn du sie 
weglässt, musst du sie durch einen * beim Datentyp ersetzen und 
umgekehrt!

Also entweder
1
void first( int begin [] )
2
{
3
  ....
4
}
oder
1
void first( int * begin )
2
{
3
  ...
4
}

aber eines von beiden muss es schon sein.
(Oder dein Compiler kocht da sein eigenes Süppchen. Aber in dem Fall ist 
das dann kein C-Compiler mehr)

> andernfalls meldet mir der compiler "error: conflicting types of
> 'first'" und markiert die eben zitierte zeile

Hast du noch irgendwo einen Prototypen, den du nicht gezeigt hast?

Es ist bei solchen Grundlagenfragen immer gut, wenn du immer dein 
komplettes Programm zeigst und nicht nur Ausschnitte. Im schlimmsten 
Fall zeigst du dann Dinge her, die nicht notwendig gewesen wären. Aber 
andersrum ist es schlimmer. Nämlich dann wenn wir Annahmen treffen 
müssen und raten müssen, was wohl in deinem Programmtext noch so alles 
steht, was wir nicht sehen können.

Im Zweifelsfall einfach das komplette C-File, so wie es ist, als 
Attachment anhängen und wir sehen alles was wir brauchen.

von Kali (Gast)


Lesenswert?

>Bei mir funktionierts nur mit
1
void first(int begin[])

>ich darf die [] also nicht weglassen.

Das hat auch nie jemand behauptet.

Du musst zwischen der Deklaration bzw. Definition einer Funktion auf der 
einen Seite und der Verwendung/dem Aufruf auf der anderen Seite 
unterscheiden.

Bei der Deklaration musst Du entweder eckige Klammern oder * verwenden 
um klarzustellen, das ein Zeiger auf ein Array übergeben wird.
Beim Aufruf hingegen ist der Name eines Arrays gleichbedeutend mit einem 
Zeiger darauf. Der Aufruf darf also kein zusätzliches * oder die eckigen 
Klammern enthalten.

von Kali (Gast)


Lesenswert?

Vielleicht hat Dich ja das hier irregeführt:

>Die eckigen Klammern bei der Funktionsdefinition sind nur syntaktischer
>Zucker.

Das heisst nicht, dass die eckigen Klammern nach Belieben weggelassen 
werden können. Vielmehr bedeutet es, das die eckigen Klammern eine 
alternative Schreibweise zu * sind. Siehe auch: 
http://de.wikipedia.org/wiki/Syntaktischer_Zucker

von gox (Gast)


Lesenswert?

@Kali: Das wars, ich konnte mit dem Begriff "Syntaktischer Zucker" 
nichts anfangen - wieder was gelernt!

@Karl Heinz Buchegger: Ich hab zum testen dieser Funktion nichts anderes 
in der main stehen, als das, was ich gepostet hab. Ich bau mir erst die 
einzelnen Funktionen und teste, ob diese funktionieren und erst zum 
Schluss bau ich daraus dann das fertige Programm.
Ist das keine übliche Vorgehensweise?

von Karl H. (kbuchegg)


Lesenswert?

gox schrieb:

> @Karl Heinz Buchegger: Ich hab zum testen dieser Funktion nichts anderes
> in der main stehen, als das, was ich gepostet hab. Ich bau mir erst die
> einzelnen Funktionen und teste, ob diese funktionieren und erst zum
> Schluss bau ich daraus dann das fertige Programm.
> Ist das keine übliche Vorgehensweise?

Doch doch. Das ist schon ok.
Ganz im Gegenteil. Das ist das, was dir viele erfahrene Programmierer 
auch empfehlen würden: Nicht zuviel auf einmal. Sieh zu, dass du schnell 
was zum Testen hast. Kleine Schritte. Aufgabenteilung. Ruhig auch mal 
ein Testprogramm mit dem man einzelne Aspekte losgelöst von der 
eigentlichen Problemstellung für sich alleine testet.

von Andreas S. (Firma: Schweigstill IT) (schweigstill) Benutzerseite


Lesenswert?

Volker Zabe schrieb:
> C:\projects_svn\winkel\test_atan\test_atan.cpp(146)
.
.
.
>         Types pointed to are unrelated; conversion requires
> reinterpret_cast, C-style cast or function-style cast

Wenn man ein Programm in C schreibt, sollte man es auch mit einem 
C-Compiler übersetzen und nicht mit einem C++-Compiler.

Karl Heinz Buchegger schreibt:
> (Oder dein Compiler kocht da sein eigenes Süppchen. Aber in dem Fall ist
> das dann kein C-Compiler mehr)

Richtig erkannt. ;-)

von Robert L. (lrlr)


Lesenswert?

>void first(int* begin)

>oder so

>void first(int begin[])

>ist beides dasselbe (die 4 da drinnen spielen sowieso keine Rolle).

als pascal programmierer dreht es einem da aber sowas von den magen 
um...

;-)

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.