Beginnersvraag: sketch werkt niet zonder pointer

Hallo,

Dit is eigenlijk een uitbereiding op vragen die ik gesteld heb op het engelstalige forum.
Ik zoek al doende hoe C++ werkt (ben een newbie) , hoe de interactie is via de IDE (en dus compiler) en hoe dit in de microcontroller terecht komt...dus hoe dit verwerkt wordt.
Mijn sketch dat ik op het engelstalig forum gepost heb was eigenlijk gewoon testen en zoeken hoe functies buiten de void loop functie kunnen gebruikt worden.
Ik probeerde twee random int's in twee losse void fucties onder te brengen om te gebruiken (Serial.println) in de void loop functie. Dit lukte.
Hetzelfde probeerde ik met een stukje tekst maar dit lukte niet (Serial.println(mysentencearray)).
Ik wil die zin (This is a sentence) op een andere plaats in de code zetten zodat ik in de toekomst makkelijk en toch overzichtelijk een stukje tekst; dat doorgestuurd wordt naar een display, kan aanpassen.
Het was alsof in de void loop functie data op de verkeerde plaats werd opgehaald want ik kreeg meestal twee lege lijnen ipv mn stukje tekst. Zowel met gebruik van een String alsook het gebruik van een char array (char mysentencearray[19]) lukte het niet.
Ik kreeg op het engelstalige forum twee werkende ( & totaal andere) oplossingen voor mijn probleem. De methode die een pointer gebruikt voor de opslag van het stukje tekst blijkt dus ook te werken op mijn sketch (=tweede sketch). Ik veronderstel misschien verkeerd dat de karakters ook in een array worden opgeslagen maar het is dan wel vreemd dat ik de haakjes [] niet hoef te gebruiken.

Kan iemand mij dus uitleggen waarom de eerste niet werkt?
Mijn (beginners)redeneringen gaan hier ergens zwaar de mist in.

Ik zet hieronder voor de duidelijkheid beide sketchen volledig.
thx!!

int returnvalue_one;
int returnvalue_two;
char mysentencearray[19];

void setup() {
  Serial.begin(9600);
  returnvalue_one = 0;
  returnvalue_two = 0;
 }

void loop() {
  randomnumberlow();
  randomnumberhigh();
  sentence();
  Serial.println(returnvalue_one);//this works
  Serial.println(returnvalue_two);//this works
  //String mysentence = "This is a sentence";
  //Serial.println(mysentence);
  //char mysentencearray[19] = "This is a sentence";//a sentence as characters in a array
  Serial.println(mysentencearray);//this works not!
  delay(500);
}

void randomnumberlow() {
  returnvalue_one = random(0,200);//random number from to
}

void randomnumberhigh() {
  returnvalue_two = random(201,400);//random number from to
}

void sentence(){
  //String mysentence = "This is a sentence";
  //Serial.println(mysentence);
  char mysentencearray[19] = "This is a sentence";//a sentence as characters in a array
  //Serial.println(mysentencearray);
}
int returnvalue_one;
int returnvalue_two;
char *mysentencearray;//or const char *mysentencearray works to

void setup() {
  Serial.begin(9600);
  returnvalue_one = 0;
  returnvalue_two = 0;
 }

void loop() {
  randomnumberlow();
  randomnumberhigh();
  sentence();
  Serial.println(returnvalue_one);//this works
  Serial.println(returnvalue_two);//this works
  //String mysentence = "This is a sentence";
  //Serial.println(mysentence);
  //char mysentencearray[19] = "This is a sentence";//a sentence as characters in a array
  Serial.println(mysentencearray);
  delay(500);
}

void randomnumberlow() {
  returnvalue_one = random(0,200);//random number from to
}

void randomnumberhigh() {
  returnvalue_two = random(201,400);//random number from to
}

void sentence(){
  //String mysentence = "This is a sentence";
  //Serial.println(mysentence);
  mysentencearray = "This is a sentence";//a sentence as characters in a array
  //Serial.println(mysentencearray);
}

dus de eerste

De eerste werkt niet omdat je verschillende variabelen met de naam mysentencearray hebt.

De eerste staat bovenaan in je programma en is een globale variabele. De tweede zit in de sentence() functie en is een lokale variabele.

Globale variabelen zijn overal in je file bekend, lokale variabelen alleen daar waar ze gebruikt worden.

Twee voorbeelden van lokale variabelen

void someFunktion()
{
  char c = 'a';
  ...
  ...
}

De variabele c is bekend binnen de functie maar niet daarbuiten.

void someFunktion()
{
  for (int cnt = 0; cnt < 10; cnt++)
  {
    char c = 'a';
    ...
    ...
  }
  ...
  ...
}

De variabele c is alleen bekend in de for-loop maar niet op een andere plaats in de functie en niet buiten de functie.

Als je meer wilt weten kun je je favoriete zoekmachine gebruiken en zoeken naar C++ scope.

Om je probleem in het eerste programma op te lossen kun je de sentence() functie aanpassen zoals hieronder getoond. Het programma maakt gebruik van de strcpy() functie

void sentence(){
  //String mysentence = "This is a sentence";
  //Serial.println(mysentence);
  strcpy(mysentencearray, "This is a sentence");//a sentence as characters in a array
  //Serial.println(mysentencearray);
}

Wees je ervan bewust dat indien de tekst groter is dan 18 karakters je in de problemen kunt komen omdat de globale variabele mysentencearray slechts 18 karakters plus de afsluitende '\0` kan bevatten en je andere delen van de gecompileerde code kunt overschrijven hetgeen kan resulteren in ongedefinieerd gedrag.

Het gebruik van strncpy() is iets veiliger maar niet perfect omdat er geen afsluitende '\0' wordt toegevoegd als de tekst te lang is en bv het afdrukken de eerste 19 karakters zal printen plus mogelijk rommel totdat de print functie een afsluitende '\0' tegenkomt.

De veiligste oplossing in je eerste programma is om snprintf() te gebruiken.

Ik had al een vermoeden omdat er iemand al iets antwoordde van een globale variabele en ik vond het vreemd dat er in tegenstelling tot de beginnersinstructies toch als het ware veel variabelen in de functie zelf voor het eerst een 'bestaan' krijgen ipv te declareren (en eventueel al een waarde te geven) buiten een fuctie (dus meestal bovenaan de code). Toch wel vervelend dat dit nergens wordt meegegeven als het over Arduino gaat want velen kennen geen C taal zoals ik. Als hobbyist heb ik niet de moed om eerst drie maanden C te studeren. Ik weet nu al welke projectjes ik wil maken maar er moet licht zijn aan het einde van de tunnel.

Vanavond zal ik nog eens checken/lezen wat strncpy() en snprintf() juist doen.

Heel hartelijk dank voor uw moeite en tijd! thx!!

aha, dus 'scope' is in deze context niet een figuurlijke uitdrukking in dat in de Engelse taal gebruik wordt als iets onder de loep nemen maar wel degelijk een term uit C++ (en mss nog andere programmeertalen)!
C++ Variable Scope

Ik begrijp je punt. Ik heb C/C++ ook zelf moeten leren en werd in mijn afstudeer project zo in het diepe gegooid, dus zonder voorkennis van C/C++ een C programma schrijven voor digitale foto bewerking (waar ik ook geen kennis van had). Gelukkig een goede mentor die me in de juiste richting kon sturen.

Zoals gezegd zijn globale variabelen overal in je file bekend. Het voordeel daarvan (in de Arduino wereld) is dat het gemakkelijk is en dat na het compileren de IDE je (voor de meeste borden) vertelt hoeveel dynamisch geheugen gebruikt is. Het nadeel is dat je ergens in een functie die globale variabelen per ongeluk kunt veranderen (typefoutje is zo gemaakt).

Lokale variabelen zijn alleen lokaal bekend (zoals je nu ervaren hebt). Dit beschermd ze tegen het per ongeluk veranderen op een andere plaats. Nadeel is dat ze niet overal bekend zijn (maar daar kun je een oplossing voor bedenken) en dat de IDE je niet kan vertellen hoeveel extra geheugen je gebruikt als je de functie met de lokale variabele gebruikt en je plotseling met te weinig geheugen zit met als resultaat onverwachte neveneffecten.

Zodra je C++ scope begrijpt heb ik een vraag. Is the variabel cnt in het tweede voorbeeld een globale of een lokale variabele :slight_smile:

volgens de code die zichtbaar is ligt cnt binnen de grens {} van de someFuction functie dus lokale variabele waar meteen een waarde wordt aan gegeven. :slightly_smiling_face:

Het is een lokale variable maar deze ligt binnen het for statement en is daarom lokaal voor de for-loop.

Je kunt het volgende testen

void setup()
{
}

void loop()
{
}

void someFunktion()
{
  for (int cnt = 0; cnt < 10; cnt++)
  {
  }
  Serial.println(cnt);
}

Dit zal een compileer fout geven.

Compilation error: 'cnt' was not declared in this scope

Weer een puzzelstukje dat past, het viel mij langzaam op dat de for statement binnen dezelfde sketch soms veelvuldig gebruikt wordt met dus altijd dezelfde int (de naam, meestal i) en dat dit geen problemen gaf tussen de int's 'i' op andere plaatsen in de code. De Serial.print zou dan eigenlijk binnen de for statement moeten staan, wat uitgevoerd wordt na het uitvoeren van een cyclus (tussen {}).
...maar het verklaard dan niet als ik in mijn eerste sketch de aanpassing doe in de eerst sketch
van

naar

het niet werkt ondanks dat het char array mysentencearray globaal gedeclareerd werd. Met de in's werkt dit wel maar met de char array dus niet. Maar wat lees ik net (via Google zoekopdracht 'C++ scope' op W3Schools.com) :
'However, you should avoid using the same variable name for both globally and locally variables as it can lead to errors and confusion.
In general, you should be careful with global variables, since they can be accessed and modified from any function'
...met bijhorend voorbeeld op die pagina.
De strncpy() (stringcopy?) en snprintf() (stringprint...?) moet ik nog testen maar door hier van de hak op de tak te springen ben ik er dus nog niet aan toe gekomen. Het lijkt er wel op dat deze twee iets doen wat gelijkt op een pointer gebruiken.

Ik ben ondertussen begonnen met alle turtorials te overlopen op W3schools.com . Hier moet ik alleen de cout en cin proberen in gedachten te vervangen door Serial.print en Serial.available

en hier krijg ik ook mijn hersenen niet rond. Ondanks alle voorbeelden snap ik de clue niet:

return

Terminate a function and return a value from a function to the calling function, if desired.

Veel kom ik tegen

return 0;

of gewoon

return

maar soms ook bvb

[quote="gcjr, post:5, topic:1339361"]
`return random (low, high);`
[/quote]


uit de sketch

// demonstrate use of functions

// -----------------------------------------------------------------------------
// function is passed low and high integers and returns random #
int
getRand (
    int low,
    int high )
{
    return random (low, high);
}

// -----------------------------------------------------------------------------
// function is passed a string, low & high integers an print a string
void
printSentence (
    const char *s,
    int         low,
    int         high )
{
    Serial.print   (s);
    Serial.println (getRand (low, high));
}

// -----------------------------------------------------------------------------
void loop()
{
    printSentence ("low random # ", 0, 200);
    printSentence ("high random # ", 200, 400);
    delay(500);
}

// -----------------------------------------------------------------------------
void setup() {
    Serial.begin(9600);
}

En ik kan alles wel een beetje linken maar ik kan niet specifiek uitleggen wat die return nu eigenlijk doet. Het enige dat ik gelezen heb is dat een return handig kan zijn in een test sketch om een functie op een bepaalde plaats te beƫindigen. Maar waardes meenemen naar 'the calling function'??? welke waarde? welke fuctie is dit dan? is dit dan een int die ergens wordt opgeslagen als het een bvb een int functie is waarin de return staat? Heel verwarrend voor een beginner, alle uitleg ten spijt.

Met een return keer je terug uit een functie naar de plek waar die functie aangeroepen werd.
Je kunt een resultaat van de functie meesturen naar de 'caller'.

int sum(int a, int b) {
    int c = a+b;
    return c;
}


void setup() {
    Serial.begin(9600);
    int a = 3;
    int b = 4;
    int d = sum(a,b);
    Serial.print(d);
}

Dit print 7 op de serial monitor...

De naam van een array is 'under the hood' een pointer naar het eerste element van die array...

char x[8];

*x is dus hetzelfde als x[0]...

Als je lokaal een variable dezelfde naam geeft als een globale variabele kun je de globale variabele niet meer bereiken. 'Shadowing' heet dat.
Dit kan heel verwarrend zijn omdat informatie uit je functie dus niet in de globale variabele terechtkomt.
Gebruik voor globale variabelen dus langere namen.
Gebruik korte namen zoals i en j alleen in lokale for loops...

Je kunt een karakter array op deze manier alleen een tekst toekennen wanneer je het karakter array declareert. Als je later een andere waarde wilt toekennen (zoals jij dat doet) of de bestaande waarde wilt veranderen moet je de c-string functies gebruiken; dat zijn alle functies die met str beginnen op deze pagina: C++ cstring Library Reference (cstring functions).

De functies op die pagina die met mem beginnen zijn meer voor gebruik met byte arrays die niet afgesloten worden met een '\0'.

Sorry maar hier begrijp ik niet veel van. Ik zal zeggen waarom (na uren weeral erop te zwoegen) waarom ik er voor mij geen logica kan in vinden als ik deze sketch leg naast een andere die hier een voorbeeld van geeft.

u moet mij corrigeren waar ik hier de mist in ga:
-de fuctie is een int fuctie met parameters voor een bewerking. nl. de variabele int a e variabele int b. Binnen de curly brackets staat de bewerking met eerst een declarering van locale variabele int c met direct erna de bewerking om de variabele int c een waarde te geven (kan ook met 'int c; int c = a+b;').
Hoe kan het dat de parameters a en b gebruikt worden in de fuctie int sum terwijl ze alleen lokaal gedeclareerd werden in de fuctie void setup? Het is alsof de regel van het lokaal declareren niet werkt. Of zijn de parameters van het datatype int in de functie int sum andere variabelen als deze in de functie void setup?
Wordt via de regel

dan de returnwaarde van functie int sum gebruikt incl. tussen de haakjes de parameters van de functie int sum? Als dit niet de parameters zijn van int sum, waarom kan de int waarde van functie int sum niet gewoon gebruikt worden voor int d met de lijn

int d = sum;

?
Waarom doe ik hier over lastig....Omdat ik het volgende ben tegen gekomen.

int myMultiplyFunction(int x, int y){

int result;

result = x * y;

return result;

}
void loop(){

int i = 2;

int j = 3;

int k;

k = myMultiplyFunction(i, j);

Serial.println(k);

delay(500);

}

Hier heb je de lijn

k = myMultiplyFunction(i, j);

De int functie myMultiplyFuction heeft hier normaal de int parameters x en y en niet de parameters i en j. ??

Ik kan er dus maar geen correcte redenering in vinden.

Breek nou mn klomp (nee ik ben een belg maar het is een grappige uitdrukking).:
Wat werkt bij mijn sketch is globale variabele en in functie void sentence:

char *mysentencearray;//or const char *mysentencearray works to
mysentencearray = "This is a sentence";//a sentence as characters in a array

met Serial.println (mysentencearray)

Wat niet werkt is dus met een char array:

char mysentencearray[19];
mysentencearray = "This is a sentence";//a sentence as characters in a array

en natuurlijk Serial.println (mysentencearray).

Maar dat is het juist wel dacht ik. Globaal: char mysentencearray[19]; zodat het ook gekend is in de functie void sentence.

De namen van de argumenten van de functie zijn misschien wat ongelukkig gekozen.

int sum(int xx, int yy) {
    int c = xx+yy;
    return c;
}

In de aanroep van de functie geef je a en b mee als argument. De functie kent deze als xx en yy.

ok dus de variabelen a & b in de fucntie void setup zijn eigenlijk de waardes die eigenlijk als parameter worden gebruikt. Dit soort dingen zijn absoluut niet failproof en ik kan me voorstellen dat er veel vergissingen gebeuren met dit soort mogelijkheden. Zoals geen int's gebruiken terwijl de functie sum deze wel nodig heeft of drie waardes doorgeven i.p.v. int d = sum(a,b,c). Ik kan me voorstellen dat die errors (of slechter, een werkende sketch met hele rare resultaten) een hel zijn om eruit te halen.
Toch weer heel erg bedankt voor dit puzzelstukje dat past.

Als je 2 floats stuurt worden die omgezet naar een int en vervolgens opgeteld...
Omzetten naar int gebeurt door naar beneden af te ronden...

float f=1.4,;
float g=7.9;
int q = sum(f,g);
Serial.print(q);

Geeft dus 8 als resultaat.

sum(i,j,h) zal een compiler error geven. De compiler zoekt naar een passende functie, en dit past dus niet...

Als je float q had gebruikt was het resultaat ook 8 geweest...

1 Like

Ik heb ondertussen beide geprobeerd en het werkt beide! Die snprintf is een soort printfuctie zoals je op de pc iets zou'afdrukken' naar een pdf file. Nog altijd heel vreemd dat dit werkt terwijl de char array alleen globaal gedefiniƫerd is. thx! ...toolbag weer iets groter