array bidimensionale

Salve a tutti !
Quello che sto cercando di fare è ottenere un array bidimensionale leggendo una serie di file di testo.

esempio:
ho tre file di testo strutturalmente tutti uguali, ognuno dei quali, all'interno, contiene 4 linee :

myfile_1 contiene :

mystring_1 = "pippo"
mystring_2 = "pluto"
mystring_3 = "paperino"
mystring_4 = "topolino"

myfile_2 contiene :

mystring_1 = "cip"
mystring_2 = "ciop"
mystring_3 = "archimede"
mystring_4 = "edi"

myfile_3 contiene :

mystring_1 = "basettoni"
mystring_2 = "bassotti"
mystring_3 = "gastone"
mystring_4 = "paperone"

Tralasciando la tecnica di lettura del file (che in questo contesto non ci interessa), vorrei ottenere il seguente risultato (cioè 12 variabili indicizzate con array 2D) all'interno dello sketch:

out[1][1] = "pippo"
out[1][2] = "pluto"
....
out[2][2] = "ciop"
....
out[3][3] = "gastone"
out[3][4] = "paperone"

dove il primo numero dell'array si riferisce al file ed il secondo alla posizione rilevata all'interno del file stesso.
Le variabili NON sono costanti.

Mi sto incartando perchè sicuramente non riesco a capire come funziona la questione e comunque (anatema!) ho in testa l'organizzazione degli array in vb, nei quali i numeri - in automatico - creano già di per se stessi una situazione tipo riga-colonna, simile a quella che vorrei realizzare io all'interno dello sketch.
Ho cercato pagine che spiegassero in modo semplice l'array 2D ma sinceramente ne sono uscito più confuso che persuaso.
Ogni aiuto è gradito, anche e soprattutto links di spiegazione "elementare" sulla questione in oggetto.
Grazie infinite

ciao...dico la mia...poi magari vengo corretto:

tu hai 3 elementi base: file, elementi nel file, lunghezza elemento.
quindi direi che ti serve array 3d tipo:

myArray[file][elemento nel file][lunghezza elemento].

dato che in arduino gli elementi nell'array multidemensionale hanno lunghezza fissa devi considerare l'elemento più "grande" ed il file con più elementi...che potrebbe voler dire tanta memoria "sprecata".

l'appoccio con array di 2 dimensioni potrebbe andare bene se salvi un puntatore alla stringa...ma vuol dire che la stringa è da "qualche altra parte".

PS: oppure crei un unico file dove in ogni riga c'è l'elemento equivalente di ogni file e navighi in questo file.

Anche io programmo in VB.
Qui siamo in C, e c'e' una differenza fondamentale dato dalle stringhe.
In VB esiste una variabile stringa e quindi array di stringhe.
In C una stringa (NON String) è già un array di char, la qual cosa complica molto.
Se parlassimo di elementi numerici, esempio int, C e VB sarebbero praticamente uguali come gestione di array/matrici (in VB non puoi usare però puntatori al posto degli indici)

Come detto da @Orso il tuo esempio out[1][1] = "pippo" è un elemento che punta a una zona di memoria (semplice puntatore), se inizializzi la matrice in dichiarazione della stessa, "pippo" stà in Flash (quindi spazio costante) assieme al codice e non allocato in Sram (memoria variabili)
Quando leggi il file puoi avere la matrice, ogni elemento punterà a una memoria allocata dinamicamente, dovrai usare malloc() a ogni riga letta, leggi la riga in un buffer, calcoli quanti caratteri+1, allochi con malloc() questo numero di char e copi da buffer a questa nuova memoria.
Ogni elemento sarà char * ( e non char perché sarebbe un singolo carattere)
Devi avere però abbastanza SRam per memorizzare tutte queste frasi dinamicamente. Su Arduino Uno ricorda hai solo 2Kb

Anzitutto grazie per le risposte.
Insomma ... non è cosa facile nè per quanto riguarda la programmazione nè per la memoria (che in ogni caso c'è perchè sto utilizzando un esp32 ; i files in tutto sono 50 - totale 200 varibili di max 10 caratteri ognuna).

@orso -- > Non posso utilizzare un file unico in quanto ogni gruppo di 4 variabili viene associato ad un pulsante e quindi se dovessi far scrivere agli "utonti" MYSTRING[28] invece di MYSTRING [7][4] si perderebbero per strada.

A questo punto mi chiedo : esiste una soluzione alternativa ?

Puoi

  1. usare una matrice di puntatori come ti ho detto, allochi con malloc esattamente i caratteri+1 necessari per ogni stringa.
    E hai la matrice come hai pensato tu.
  2. in alternativa puoi usare un array di record (struct)
    se crei un record del tipo struct { int nFile; char cFrase[11]; }
    puoi creare un array di x elementi di quella struttura. nFile indica da quale file lo hai letto. ovvio cFrase allocato per solo 10 char più fine stringa. Con un pò di spreco, visto che "pippo" ne usa 5 su 10

Ma... tu sai la dimensione totale esatta ? Varia ? Perché se non è sempre la stessa devi per forza usare allocazione dinamica (malloc)

ok ... studio entrambe le cose perchè non ho mai fatto una cosa del genere.
vediamo se ne esco vivo :o .

Quello che tu hai in mente come array bidimensionale è corretto:
out[1][1] = ...
Ma non sei in VB dove ogni elemento è uno string
Qui in C ogni elemento è un char* e dovrai assegnare a quel elemento il puntatore ritornato da malloc() che crea lo spazio necessario in memoria (sono poi in realtà cose che vb fa in automatico di nascosto)

dichiara un array buf di char abbastanza grande per leggere da file

  • leggi stringa da file in buf, strlen() ti dice quanti caratteri ci sono
  • poi fai malloc() di quel numero di caratteri + 1 e assegni quello spazio di memoria a elemento matrice:
char* newmemoria;
newmemoria = (char*)malloc( strlen(buf)+1 );       // crea spazio di memoria
if(newmemoria==NULL)                                        // se null errore in creazione, mem piena ?
{ // errore !!, non riuscito a creare memoria
}
else
{ strcpy(newmemoria,buf);          //copi i dati da buf a quella memoria:    
  out[idfile][idrow]=newmemoria;
  idrow++;
}

@ nid69ita
Come detto sono poco esperto quando andiamo a programmare in questo modo...
tanta buona volontà . Ho cercato di capire la questione proposta da te ma mi sono perso.
Nel senso, la vedo troppo incasinata.
Ripeto : ho una brutta mentalità : quella delle cose più semplici dettate una volta dal vb6 ed ora da vstudio.

Prima di arrivare qui, mi sono imbattuto in un esempio che ora posto, che però avevo accantonato per vedere se ce ne fosse uno più adeguato:

void VEDI_ARRAY() 
{
   CUSTOM_ARRAY_SETUP(arr_b);
   Serial.println(arr_b[1][4]);
   Serial.println(arr_b[4][4]);
   
}

void CUSTOM_ARRAY_SETUP (String arr_B[4][5])
{

   arr_B[1][1] = "CiaoCiao11";  
   arr_B[1][2] = "MondoMon12";
   arr_B[1][3] = "NOT";  
   arr_B[1][4] = "MondoMon14";
.....
   arr_B[4][1] = "CiaoCiaoAA";  
   arr_B[4][2] = "MondoMondo";
   arr_B[4][3] = "CiaoCiaoAA";  
   arr_B[4][4] = "MondoMon44;"
}

In uscita ho le giuste risposte.

Ho osato e sono arrivato a arr_B[51][5] :ho notato che la memoria per i programmi mi ha preso in più 2144 bytes mentre quella dinamica 3064.

Mi chiedo : cosa c'è di sbagliato in questo tipo di programmazione ?
Voglio capire veramente perchè chi ha creato questo esempio non ha usato quello proposto da te.
Nel senso : quali sono le differenze e perchè usare l'uno o l'altro ?

Nel codice che hai trovato c'e' il problema dell'uso dell'oggetto String.
In Arduino c'e' la libreria interna al core che mette a disposizione la classe String per creare oggetti String.
Nel VB hai le variabili di tipo base String, che sono gestiti di nascosto da VB. Inoltre sei su un PC che ha prima di tutto Giga di RAM e poi ha un sistema operativo che fa pulizia della memoria ogni tanto (o il S.O. oppure il VB). Si chiama garbage collection (pulizia della spazzatura).
In C non c'e' qualcosa di nascosto che fa garbage collection. Il C è il linguaggio basico per scrivere gli altri linguaggi e i sistemi operativi (+parti in assebly)
Il pericolo in C, soprattutto su MCU piccoline è che usando String e alloca/disalloca, la memoria si micro-frammenta (magari esp32 ha più RAM e quindi è più difficile accorgersi)
Quando hai una String Arduino di 10 caratteri, se gli aggiungi 3 caratteri, in realtà di sotto al cofano, viene allocato nuova memoria x 13 char, e poi viene deallocato lo spazio precedente di 10; fatto di continuo la memoria si micro-frammenta, ovvero si creano spazi troppo piccoli per poterli sfruttare.

Codice parziale e non provato, da sviluppare controllo della lettura dei file:

#include <SD.h>

int AllocaFrase(byte p_IdxFile, byte p_IdxCnt, char* p_Buf, char* p_Matrice[3][4])
{ char* newmemoria;
  newmemoria = (char*)malloc( strlen(p_Buf) + 1 );     // crea spazio di memoria
  if (newmemoria == NULL)                                     // se null errore in creazione, mem piena ?
  { return 0;  // falso  // errore !!, non riuscito a creare memoria
  }
  else
  { strcpy(newmemoria, p_Buf);         //copi i dati da buf a quella memoria:
    p_Matrice[p_IdxFile][p_IdxCnt] = newmemoria;
    return 1;     // vero
  }
}
char* matrice[3][4];     // 0-2=3 file; 0-3=4 frasi per file

void setup() {
  byte IdxCnt, IdxChar;
  char buf[20], c;
  File dataFile;

  Serial.begin(9600);
  if (!SD.begin(10))   // pin CS=10
  { Serial.println("Avvio SD Card fallito!");
    while(1);
  }
  Serial.println("SD Card avviata.");
  // leggo primo file
  IdxCnt = 0;
  IdxChar = 0;
  dataFile = SD.open("primo.txt", FILE_READ);
  if (dataFile)
  { // ciclo 1 lettura riga in buf
    while (dataFile.available())
    { c = dataFile.read();
      if (c == '\r')
      {}
      else if (c == '\n')
      { buf[IdxChar] = c;
        IdxChar++;    // con if si potrebbe controllare frase troppo lunga
      }
      else
      { buf[IdxChar] = 0;
        AllocaFrase(0, IdxCnt, buf, matrice);
        IdxChar = 0;
        IdxCnt++;
        if (IdxCnt >= 4) break;
      }
    }
    dataFile.close();
  }
  else
  { Serial.println("Non letto file primo.txt");
  } // fine ciclo 1
  IdxCnt = 0;
  IdxChar = 0;
  dataFile = SD.open("secondo.txt", FILE_READ);
  if (dataFile)
  { // ciclo 2 lettura riga in buf
    while (dataFile.available())
    { c = dataFile.read();
      if (c == '\r')
      {}
      else if (c == '\n')
      { buf[IdxChar] = c;
        IdxChar++;    // con if si potrebbe controllare frase troppo lunga
      }
      else
      { buf[IdxChar] = 0;
        AllocaFrase(1, IdxCnt, buf, matrice);
        IdxChar = 0;
        IdxCnt++;
        if (IdxCnt >= 4) break;
      }
    }
    dataFile.close();
  }
  else
  { Serial.println("Non letto file secondo.txt");
  } // fine ciclo 2
  IdxCnt = 0;
  IdxChar = 0;
  dataFile = SD.open("terzo.txt", FILE_READ);
  if (dataFile)
  { // ciclo 3 lettura riga in buf
    while (dataFile.available())
    { c = dataFile.read();
      if (c == '\r')
      {}
      else if (c == '\n')
      { buf[IdxChar] = c;
        IdxChar++;    // con if si potrebbe controllare frase troppo lunga
      }
      else
      { buf[IdxChar] = 0;
        AllocaFrase(2, IdxCnt, buf, matrice);
        IdxChar = 0;
        IdxCnt++;
        if (IdxCnt >= 4) break;
      }
    }
    dataFile.close();
  }
  else
  { Serial.println("Non letto file terzo.txt");
  } // fine ciclo 3
}

void loop() {
  // put your main code here, to run repeatedly:
  for (int f = 0; f < 3; f++)
  { for (int c = 0; c < 4; c++)
    { Serial.print(f);
      Serial.print(',');
      Serial.print(c);
      Serial.print('=');
      Serial.println(matrice[f][c]);
    }
  }
  delay(5000);
}

nid69ita:
Nel codice che hai trovato c'e' il problema dell'uso dell'oggetto String....
....
... fatto di continuo la memoria si micro-frammenta, ovvero si creano spazi troppo piccoli per poterli sfruttare.

Grazie infinite per la spiegazione ...
Se tanto mi dà tanto succede che ad un certo punto mi si freeza tutto per mancanza di memoria e serve un bel reset per ricominciare.
E non è una bella cosa.
Domanda da un milione di dollari : ma se io carico gli oggetti string all'inizio solo una volta e li tengo buoni buoni sempre in memoria utilizzandoli solo per la lettura, succede lo stesso quello che dici tu ?
Domanda da studente elementare ma perdonami voglio capire !
Grazie per il tuo codice. Sono molto curioso di provarlo. Lo faccio domani stesso.

Non dovrebbe succedere, ogni String è una "capsula" che contiene il puntatore al blocco di char.

Io ho dei dubbi:
Ma se non puoi usare un file unico significa che il file lo devi scegliere a runtime?
Perché se così fosse non ti serve una matrice così grande
Facciamo cosi:
Spiega bene quello Che vuoi fare, non Come lo vuoi fare

Purtroppo non credo che si possa dire che lo voglio fare in qualche specifico modo: ritengo sia l'unico possibile. Spero di sbagliarmi.
In ogni caso lo scenario è presto detto, sperando di esporre chiaramente quanto finora ho fatto ...

Sto costruendo una pedaliera per chitarra (con usb host, midi etc etc.) che ha 50 preset.
I suddetti preset sono gestiti da 2 pulsanti up&down + 4 pulsanti preset .
I 2, come detto, permettono di fare up&down da 1 a 50 step 4.
Quindi in pratica i 4 dei preset saranno di volta in volta : 1-4 --- 5-8 ---- 9-12 etc etc a seconda di come si premono i 2.
Poi ci sono anche altri 4 pulsanti (che chiamo custom), abbinati tutti e quattro al preset scelto.
Se per esempio scelgo il preset 34, i 4 pulsanti custom (in questa situazione) sono abbinati appunto al preset 34 ed ognuno di questi 4 custom deve svolgere una funzione diversa.

Quindi in pratica abbiamo : 50 preset per 4 custom = 200 custom

Il chitarrista, per poter decidere che il 3° custom del preset 34 deve fare una cosa, dovrà aprire il file di testo 34.txt ed inserire dentro una riga tipo custom[3] = "cosadafare".
Se decide che il 2* custom del preset 12 deve fare una cosa, apre 12.txt e scrive custom[2]="altra cosa"
etc etc.
nota : il file di testo citato sta su una SD che il chitarrista configura da pc.

E fin qui scenario e programmazione esterna.

Quando parte, lo sketch, per ovvi motivi di abbattimento delle latenze, si deve mettere in memoria tutte le info di ogni singolo file.
I files debbono essere NECESSARIAMENTE 50, per facilitare il setup del chitarrista.
Ogni file contiene una decina di info che poi verranno trasformate in variabili.
Tra queste, 4 sono dei pulsanti custom.

Se ne deduce che dentro lo sketch dovrò necessariamente utilizzare (per il problema oggetto del tread) una matrice bidimensionale, col primo numero che riguarda il file (1-50) e il secondo che riguarda la posizione custom (1-4).

Ecco il motivo dei files separati e delle matrici 2D.

p.s. non dite che è una cosa da pazzi ma mi è stato chiesto da un chitarrista. il progetto è già a buon punto e mi manca poco per la fine del prototipo. Capire come gestire questa cosa mi aiuterebbe di certo a gestire al meglio le varie limitazioni che la mia ignoranza potrebbe porre come paletti per il corretto funzionamento.

Ok, chiaro
Spezziamo in due il problema:
matrice di stringhe
Leggerle da file
Matrice di stringhe:
Non puoi dichiararla:
char * matrice [50][4];
Perché poi dovresti inizializzare 200 puntatori a carattere su 200 aree allocate con malloc ()
Non è il caso
Però puoi fare:
char matrice [50][4][11]={0};
Notare 11 come dimensione, per contare il terminatore
E le puoi indirizzare come se fossero dichiarate nell'altra maniera
Ovvero puoi eseguire cose come:
Serial.printl (matrice[0][0]);
Per quanto invece riguarda la lettura dal file é molto banale
Hai dato uno sguardo al nostro "aiutateci ad aiutarvi"?
Se seguì i suoi link sei a posto

Esattamente dove trovi difficoltà?

@ Standardoil
non ho problemi di lettura del file... forse è la prima cosa che ho fatto dai tempi di arduino r3 (8 anni fa ?) ::slight_smile: . Proverò a tempo debito il codice di @nid69ita (che ringrazio).
E, in fondo, cosa debbo risolvere ? Come accennato all'inizio, vorrei solo cercare di capire come levarmi di dosso (in questa specifica situazione) i panni che vb mi ha fatto vestire da quando avevo 30 anni ad oggi (che ne ho 60).
So bene che è difficile e per questo faccio scansione della valida esperienza di chi, nel mondo di questo linguaggio, ci sta da tanto o se ci sta da poco ha mente più elastica della mia.

Insomma, nel caso specifico, il "problema" è capire come gestire 'ste 200 variabili per far si che si possano trovare in modo - diciamo - indicizzato e sopratutto non creino problemi di memoria o di qualsiasi altro genere quando lo sketch va in run.
Pensavo(speravo) di agire bene, ma a quanto pare usare "string" non va bene. O almeno, andrebbe bene ma potrebbe creare dei freeze.
In base ai vostri suggerimenti studio e faccio test con char ...

Standardoil:
Però puoi fare:
char matrice [50][4][11]={0};

puoi eseguire cose come:
Serial.printl (matrice[0][0]);

Ho parlato

Per riempire gli array, visto che di certo non ci sono NULL nel comando, uso strcopy o in ogni caso è sempre meglio usare memcopy ?

Stringhe

Sempre strcpy

I due comandi hanno una grande differenza .
strcpy() cicla e copia i caratteri fino a che non incontra un fine stringa ovvero NULL (valore 0)
memcpy() invece copia quanti byte tu gli dici, non sa nulla di un possibile valore NULL di fine stringa. E devi stare attento con stringhe (classiche del C) se strlen(buf) dice 4, tu dovrai copiare 5 byte con memcpy() perché il 5 byte è il NULL, strlen() non lo conta.