Creating a memcmp_E function [SOLVED]

compiler: Arduino IDE 1.8.2

template
void whatsMySize(T s)
{
  Serial.print(F("Size of param = "));
  Serial.println(sizeof(s));
}

template void whatsMySize<>(uint8_t); // instantiates whatsMySize(uint8_t), template argument deduced
template void whatsMySize(int); 
template void whatsMySize(double); // instantiates whatsMySize(double)


void setup() {
  uint8_t b=1;
  int i=1;
  double d=1;

  Serial.begin(115200);
  whatsMySize(b);
  whatsMySize(i);
  whatsMySize(d);
}

void loop() {}

Seems to me the easiest thing to do is just always tell the function how many bytes to compare. That way you don't need to mess with templates or function overloading. You can use the 'sizeof' directive to set the byte count in each call to the function.

I tried with 1.8.8 on a uno... May be your compiler version ?

gfvalvo: Seems to me the easiest thing to do is just always tell the function how many bytes to compare. That way you don't need to mess with templates or function overloading. You can use the 'sizeof' directive to set the byte count in each call to the function.

Sure but it’s not fun :). (But would match what memcmp() does as it takes 2 pointers and the number of bytes to compare)

J-M-L: I tried with 1.8.8 on a uno... May be your compiler version ?

Could 1.8.2 and 1.8.8 reside on the same machine without conflict?

I presume if I install 1.8.8 it will not destroy the existing sketch folder? Which I just backed up to external drive.

I believe You can indeed have multiple versions of the IDE. A backup is always a good idea :)

gfvalvo: Seems to me the easiest thing to do is just always tell the function how many bytes to compare. That way you don't need to mess with templates or function overloading. You can use the 'sizeof' directive to set the byte count in each call to the function.

J-M-L: Sure but it’s not fun :).

What @J-M-L said! However, I am now considering a compromise - making the calling convention something like memcmp_EB * and *memcmp_EW * for bytes and words respectively. This appeals to me because it would somewhat follow the AVR naming, à la *pgm_read_byte , pgm_read_word , etc. making it more self-documenting and at the same time, as @gfvalvo states, "tell the function how many bytes to compare". It seems this might could simplify the under-the-hood stuff too.

I guess it'd be fun. You could use function overloading...

Note that you're being non-standard; memcmp() is a very old and very standardize function that is well defined to compare byte strings. There won't be any speed difference; both EEPROM and RAM are byte-wide and can only actually fetch one byte at a time.

westfw: You could use function overloading...

I thought for overloading to work the argument lists must be distinguishable from one another. What I'm thinking is the same argument types (addresses & length) for any of the allowable types.

westfw: Note that you're being non-standard; memcmp() is a very old and very standardize function that is well defined to compare byte strings. There won't be any speed difference; both EEPROM and RAM are byte-wide and can only actually fetch one byte at a time.

Yes, I understand. That's why the iterator in the loop which calls eeprom_read_byte must vary according to the caller's type.

My latest effort:

.ino

/*
   Attempt to duplicate memcmp_P but for EEPROM
   V1.6 accomodate 1-byte, 2-byte, and 4-byte arguments and
   is converted to library format


   https://www.avrfreaks.net/forum/tut-c-using-eeprom-memory-avr-gcc
*/

#include "memcmp_E.h"

void setup() {

  Serial.begin(9600);
  // ram_data[] based on combo lock code starting at 0020d 12/6/18
  byte ram_data[] = {1, 7, 3, 1, 9, 5};
  // EEPROM data  =  1, 7, 3, 1, 9, 0
  int EEPROM_addr = 20;
     
  // compare bytes
  if ((memcmp_E(ram_data, EEPROM_addr, 6))) Serial.print("Data mismatch \n");
  else Serial.print("Data matches \n");
  //
  if ((memcmp_E(ram_data, EEPROM_addr, 5))) Serial.print("Data mismatch \n");
  else Serial.print("Data matches \n");

  // compare words
  Serial.print("\n\n");
  if (memcmp_EW(ram_data, EEPROM_addr, 2)) Serial.println("word mismatch");
  else Serial.print("word match\n");
  if (memcmp_EW(ram_data, EEPROM_addr, 3)) Serial.println("word mismatch");
  else Serial.print("word match\n");

 // compare doubles
  Serial.print("\n\n");
  if (memcmp_ED(ram_data, EEPROM_addr, 1)) Serial.println("double mismatch");
  else Serial.print("double match\n");
  if (memcmp_ED(ram_data, EEPROM_addr, 2)) Serial.println("double mismatch");
  else Serial.print("double match\n");
}

void loop() {}

.cpp

/*
   Operates like memcmp_P but works in the EEPROM space.
   V1.6

   https://www.avrfreaks.net/forum/tut-c-using-eeprom-memory-avr-gcc
*/
#include "memcmp_E.h"
#include 

// Compares n bytes of EEPROM with RAM variable.
// Caller provides:
// RAM address, EEPROM address, number of elements to test
// Function exits on first mismatch.
// Returns false if all bytes match, true if any mismatch detected.
//
bool memcmp_E(uint8_t* ramAddr, uint16_t EE_addr, uint8_t len) {
  for (int i = 0; i < len; i++) {
    uint8_t EE_data = eeprom_read_byte((uint8_t*)EE_addr++);
    if (*ramAddr++ - EE_data != 0)
      return true;
  }
  return false;
};

bool memcmp_EW(uint8_t* ramAddr, uint16_t EE_addr, uint8_t len) {
  for (int i = 0; i < len*2; i++) {
    uint8_t EE_data = eeprom_read_byte((uint8_t*)EE_addr++);
    if (*ramAddr++ - EE_data != 0)
      return true;
  }
  return false;
};

bool memcmp_ED(uint8_t* ramAddr, uint16_t EE_addr, uint8_t len) {
  for (int i = 0; i < len*4; i++) {
    uint8_t EE_data = eeprom_read_byte((uint8_t*)EE_addr++);
    if (*ramAddr++ - EE_data != 0)
      return true;
  }
  return false;
};

.h

/*
   Attempt to duplicate memcmp_P but for EEPROM
     
   https://www.avrfreaks.net/forum/tut-c-using-eeprom-memory-avr-gcc
*/
#include 

#ifndef memcmp_E_h
#define memcmp_E_h

class memcmp_E {

  public:
    memcmp_E(); //constructor    
    uint8_t* ramAddr;
    uint16_t EE_addr;
    uint8_t EE_data;
    uint8_t len;
    uint8_t elementSize;
    uint16_t i;
};
bool memcmp_E(uint8_t*, uint16_t, uint8_t);

class memcmp_EW {

  public:
    memcmp_EW(); //constructor    
    uint8_t* ramAddr;
    uint16_t EE_addr;
    uint8_t EE_data;
    uint8_t len;
    uint8_t elementSize;
    uint16_t i;
};
bool memcmp_EW(uint8_t*, uint16_t, uint8_t);

class memcmp_EDW {

  public:
    memcmp_ED(); //constructor    
    uint8_t* ramAddr;
    uint16_t EE_addr;
    uint8_t EE_data;
    uint8_t len;
    uint8_t elementSize;
    uint16_t i;
};
bool memcmp_ED(uint8_t*, uint16_t, uint8_t);

#endif

No claim to elegance but it does work.

I thought for overloading to work the argument lists must be distinguishable from one another.

I'm pretty sure that an int* and char* are distinguishable. This looks like it works:

int memcp(char*p1, char*p2, size_t l) {
    return memcmp(p1, p2, l);
}

int memcp(int*p1, int*p2, size_t l) {
    return  memcmp((char*)p1, (char*)p2, l*2);
}

char c1[] = "this is a test";
char c2[] = "another string";
int i1[5] = {1, 2, 3, 4, 5};
int i2[5] = {1, 2, 7, 6, 5};

int main() {
    printf("%d\n", memcp(c1, c2, sizeof(c1)));
    printf("%d\n", memcp(i1, i2, 5));
}

westfw:
This looks like it works:

There are gaps in my knowledge. How do I print the results on the serial monitor using printf()?

Never mind, I modified it to use Serial.print(). Testing.

This is a quick hack if what I had in mind with templated functions

start by running this once on a UNO to establish EEPROM values for addresses between 10 and 21

#include 

void setup() {
  const uint32_t v1 = 0xDEADBEEF;
  const uint32_t v2 = 0xBADCAFFE;
  const uint32_t v3 = 0xFEE4ABED;

  // do this once to establish EEPROM values
  EEPROM.put(10, v1);
  EEPROM.put(14, v2);
  EEPROM.put(18, v3);
}

void loop() {}

then run this code

// -------------------------------------------------
// TEMPLATE DECLARATION
// -------------------------------------------------
#include 

template
int memcmp_E(const T* source, int eeprom_start, size_t  len )
{
  int result = 0;
  uint8_t sample[sizeof(T)];
  int eeAddress = eeprom_start;
  for (size_t i = 0; i < len * sizeof(T); i += sizeof(T)) {
    EEPROM.get( eeAddress + i, sample);

    Serial.print(F("Reading EEPROM @ address:"));  Serial.print((int) eeAddress + i); Serial.print(F("\t0x"));
    for (int j = sizeof(T) - 1; j >= 0 ; --j) {
      Serial.print(sample[j], HEX);
    }
    Serial.println();

    result = memcmp ( ((uint8_t *) source) + i, (uint8_t *) sample, sizeof(T) );
    if (result) break;
  }

  return result;
}

// teach the compiler about byte or unsigned long comparisons

template int memcmp_E<>(const uint8_t*, int, size_t);
template int memcmp_E<>(const uint32_t*, int, size_t);


// -------------------------------------------------


const uint32_t v1 = 0xDEADBEEF;
const uint32_t v2 = 0xBADCAFFE;
const uint32_t v3 = 0xFEE4ABED;

uint8_t aByte = 0xEF;
uint8_t aBadByte = 0xFE;

uint8_t aByteArray[] = {0xEF, 0xBE, 0xAD, 0xDE}; // because of LITTLE ENDIAN architecture
const size_t nbItemsInaByteArray = sizeof(aByteArray) / sizeof(aByteArray[0]);

uint32_t anUnsignedLongArray[] = {v1, v2, v3};
const size_t nbItemsInanUnsignedLongArray = sizeof(anUnsignedLongArray) / sizeof(anUnsignedLongArray[0]);

uint32_t aBadUnsignedLongArray[] = {v1, v3};
const size_t nbItemsInaBadUnsignedLongArray = sizeof(aBadUnsignedLongArray) / sizeof(aBadUnsignedLongArray[0]);



void setup() {
  Serial.begin(115200);
  if (!memcmp_E(&aByte, 10, 1)) Serial.println(F("test on aByte OK")); else Serial.println(F("test on aByte NOT OK"));
  Serial.println();

  if (!memcmp_E(&aBadByte, 10, 1)) Serial.println(F("test on aBadByte OK")); else Serial.println(F("test on aBadByte NOT OK"));
  Serial.println();

  if (!memcmp_E(aByteArray, 10, nbItemsInaByteArray)) Serial.println(F("test on aByteArray OK")); else Serial.println(F("test on aByteArray NOT OK"));
  Serial.println();

  if (!memcmp_E(anUnsignedLongArray, 10, nbItemsInanUnsignedLongArray)) Serial.println(F("test on anUnsignedLongArray OK")); else Serial.println(F("test on anUnsignedLongArray NOT OK"));
  Serial.println();

  if (!memcmp_E(aBadUnsignedLongArray, 10, nbItemsInaBadUnsignedLongArray)) Serial.println(F("test on aBadUnsignedLongArray OK")); else Serial.println(F("test on aBadUnsignedLongArray NOT OK"));

}

void loop() {}

You should see this in the Serial Console:

[color=purple]Reading EEPROM @ address:10   0xEF
test on aByte OK

Reading EEPROM @ address:10 0xEF
test on aBadByte NOT OK

Reading EEPROM @ address:10 0xEF
Reading EEPROM @ address:11 0xBE
Reading EEPROM @ address:12 0xAD
Reading EEPROM @ address:13 0xDE
test on aByteArray OK

Reading EEPROM @ address:10 0xDEADBEEF
Reading EEPROM @ address:14 0xBADCAFFE
Reading EEPROM @ address:18 0xFEE4ABED
test on anUnsignedLongArray OK

Reading EEPROM @ address:10 0xDEADBEEF
Reading EEPROM @ address:14 0xBADCAFFE
test on aBadUnsignedLongArray NOT OK
[/color]

J-M-L: then run this code

This line:

int memcmp_E(const T* source, int eeprom_start, size_t  len )

returns with these errors -

jml_template_display:10: error: 'T' does not name a type

 int memcmp_E(const T* source, int eeprom_start, size_t  len )

                    ^

C:\Users\Owner\Documents\Arduino\a- memcmp_E collection\jml_template_display\jml_template_display.ino: In function 'int memcmp_E(const int*, int, size_t)':

jml_template_display:13: error: 'T' was not declared in this scope

   uint8_t sample[sizeof(T)];

                         ^

jml_template_display:16: error: 'sample' was not declared in this scope

     EEPROM.get( eeAddress + i, sample);

                                ^

Using library EEPROM at version 2.0 in folder: C:\Program Files (x86)\Arduino\hardware\arduino\avr\libraries\EEPROM 
exit status 1
'T' does not name a type

Needless to say, I don't know what to do about it. Could this be another manifestation of IDE version issues?

dougp:
Could this be another manifestation of IDE version issues?

Not version but core design philosophy. The IDE is messing things up by “helpfully” providing (incorrect) prototypes. The code compiles as posted with Eclipse / Sleober. Easiest thing to do if using Arduino IDE is to break the template definition into a .h file and then include that.

main.ino:

#include <EEPROM.h>
#include "Header.h"

const uint32_t v1 = 0xDEADBEEF;
const uint32_t v2 = 0xBADCAFFE;
const uint32_t v3 = 0xFEE4ABED;

uint8_t aByte = 0xEF;
uint8_t aBadByte = 0xFE;

uint8_t aByteArray[] = {0xEF, 0xBE, 0xAD, 0xDE}; // because of LITTLE ENDIAN architecture
const size_t nbItemsInaByteArray = sizeof(aByteArray) / sizeof(aByteArray[0]);

uint32_t anUnsignedLongArray[] = {v1, v2, v3};
const size_t nbItemsInanUnsignedLongArray = sizeof(anUnsignedLongArray) / sizeof(anUnsignedLongArray[0]);

uint32_t aBadUnsignedLongArray[] = {v1, v3};
const size_t nbItemsInaBadUnsignedLongArray = sizeof(aBadUnsignedLongArray) / sizeof(aBadUnsignedLongArray[0]);



void setup() {
  Serial.begin(115200);
  if (!memcmp_E(&aByte, 10, 1)) Serial.println(F("test on aByte OK")); else Serial.println(F("test on aByte NOT OK"));
  Serial.println();

  if (!memcmp_E(&aBadByte, 10, 1)) Serial.println(F("test on aBadByte OK")); else Serial.println(F("test on aBadByte NOT OK"));
  Serial.println();

  if (!memcmp_E(aByteArray, 10, nbItemsInaByteArray)) Serial.println(F("test on aByteArray OK")); else Serial.println(F("test on aByteArray NOT OK"));
  Serial.println();

  if (!memcmp_E(anUnsignedLongArray, 10, nbItemsInanUnsignedLongArray)) Serial.println(F("test on anUnsignedLongArray OK")); else Serial.println(F("test on anUnsignedLongArray NOT OK"));
  Serial.println();

  if (!memcmp_E(aBadUnsignedLongArray, 10, nbItemsInaBadUnsignedLongArray)) Serial.println(F("test on aBadUnsignedLongArray OK")); else Serial.println(F("test on aBadUnsignedLongArray NOT OK"));

}

void loop() {}

Header.h:

#ifndef HEADER_H
#define HEADER_H

template<typename T>
int memcmp_E(const T* source, int eeprom_start, size_t  len )
{
  int result = 0;
  uint8_t sample[sizeof(T)];
  int eeAddress = eeprom_start;
  for (size_t i = 0; i < len * sizeof(T); i += sizeof(T)) {
    EEPROM.get( eeAddress + i, sample);

    Serial.print(F("Reading EEPROM @ address:"));  Serial.print((int) eeAddress + i); Serial.print(F("\t0x"));
    for (int j = sizeof(T) - 1; j >= 0 ; --j) {
      Serial.print(sample[j], HEX);
    }
    Serial.println();

    result = memcmp ( ((uint8_t *) source) + i, (uint8_t *) sample, sizeof(T) );
    if (result) break;
  }

  return result;
}

// teach the compiler about byte or unsigned long comparisons

template int memcmp_E<>(const uint8_t*, int, size_t);
template int memcmp_E<>(const uint32_t*, int, size_t);

#endif

Note - this compiles, but I didn’t test it.

Also, the code compiles fine without these two lines:

template int memcmp_E<>(const uint8_t*, int, size_t);
template int memcmp_E<>(const uint32_t*, int, size_t);

I'm guess the compiler can figure things out from the calling context without the specialization. Although, again, I only compiled. Didn't run it.

gfvalvo: Not version but core design philosophy. The IDE is messing things up by "helpfully" providing (incorrect) prototypes. The code compiles as posted with Eclipse / Sleober. Easiest thing to do if using Arduino IDE is to break the template definition into a .h file and then include that.

Note - this compiles, but I didn't test it.

Thank you! Yes, moving the template as you describe cures it. Ran it and got identical results to your post above. Now, more perusing to understand what's going on.

Edit: I just noticed the cutesy letter sequences for the uint32 variables! ;D

Hexspeak is fun :slight_smile:

The code I posted compiled for me on a Mac with the latest Arduino IDE without warning and runs as posted

Weird

westfw: Note that you're being non-standard;

Is this because of returning only true/false or something else?

non-standard;

Is this because of returning only true/false

Yes. Although, I think I thought you were going to return "true" for "match" (which would be opposite of the standard memcmp())

westfw:
Although, I think I thought you were going to return “true” for “match” (which would be opposite of the standard memcmp())

No, I did get that part right. But, although my original thought *was *to return only T/F, that’s been amended. It turns out the cost, if one looks at it that way, of comporting to memcmp() by returning a value is insignificant since memcmp() is doing the work anyway.

I wrestled with the overloaded function approach but could not work out how to deal with endianness. Doubtless ignorance on my part.

What appears below is essentially @J-M-L’s template solution with a few changed names, another test or two, and some added commentary.

.ino

// by J-M-L http://forum.arduino.cc/index.php?topic=584108.msg3978283#msg3978283

// version with print frills removed.
// -------------------------------------------------
// TEMPLATE DECLARATION
// -------------------------------------------------
#include <EEPROM.h>
#include "Header.h"

// -------------------------------------------------

const uint32_t v1 = 0xDEADBEEF; // establish 32-bit values
const uint32_t v2 = 0xBADCAFFE;
const uint32_t v3 = 0xFEE4ABED;

// these strings start at eeprom addr 100
char ts1[] = "test str 1"; // each is eleven bytes total
char ts2[] = "test str 2";
char ts3[] = "test str 1";

uint8_t aByte = 0xEF;
uint8_t aBadByte = 0xFE;
uint8_t combo[] = {2, 2, 1, 0, 0}; // current lock combination in EEPROM

uint8_t aByteArray[] = {0xEF, 0xBE, 0xAD, 0xDE}; // because of LITTLE ENDIAN architecture
const size_t nbItemsInaByteArray = sizeof(aByteArray) / sizeof(aByteArray[0]);

uint32_t anUnsignedLongArray[] = {v1, v2, v3};
const size_t nbItemsInanUnsignedLongArray = sizeof(anUnsignedLongArray) / sizeof(anUnsignedLongArray[0]);

uint32_t aBadUnsignedLongArray[] = {v1, v3};
const size_t nbItemsInaBadUnsignedLongArray = sizeof(aBadUnsignedLongArray) / sizeof(aBadUnsignedLongArray[0]);

void setup() {
  Serial.begin(9600);
  //  Serial.println("Stored data used for compares:");
  //  Serial.println("const uint32_t v1 = 0xDEADBEEF @ EEPROM addr 8");
  //  Serial.println("const uint32_t v2 = 0xBADCAFFE @ EEPROM addr 12");
  //  Serial.println("const uint32_t v3 = 0xFEE4ABED @ EEPROM addr 16");
  //  Serial.println("aByteArray[] = {0xEF, 0xBE, 0xAD, 0xDE}\n");
  int EEPROM_addr = 8;
  if (!memcmp_E(&aByte, EEPROM_addr, 1)) Serial.println(F("test on aByte OK")); else Serial.println(F("test on aByte NOT OK"));
  Serial.println();

  if (!memcmp_E(&aBadByte, 8, 1)) Serial.println(F("test on aBadByte OK")); else Serial.println(F("test on aBadByte NOT OK"));
  Serial.println();

  if (!memcmp_E(aByteArray, 8, nbItemsInaByteArray)) Serial.println(F("test on aByteArray OK")); else Serial.println(F("test on aByteArray NOT OK"));
  Serial.println();

  if (!memcmp_E(anUnsignedLongArray, 8, nbItemsInanUnsignedLongArray)) Serial.println(F("test on anUnsignedLongArray OK")); else Serial.println(F("test on anUnsignedLongArray NOT OK"));
  Serial.println();

  if (!memcmp_E(aBadUnsignedLongArray, 8, nbItemsInaBadUnsignedLongArray)) Serial.println(F("test on aBadUnsignedLongArray OK")); else Serial.println(F("test on aBadUnsignedLongArray NOT OK"));
  //string compare
  if (!memcmp_E(ts2, 111, 5)) Serial.println(F("\ntest on string OK")); else Serial.println(F("\ntest on string NOT OK"));
  //  Serial.println();
  if (!memcmp_E(combo, 20, 5)) Serial.println(F("\ntest on string OK")); else Serial.println(F("\ntest on string NOT OK"));
}

void loop() {}

.h

// memcmp_E function
//
// by J-M-L http://forum.arduino.cc/index.php?topic=584108.msg3978283#msg3978283
// -------------------------
// Takes: a pointer to a RAM address, eeprom address, and length.
// Function compares the two pairs of addresses for
// len elements and returns true/false status of
// comparison.  Since memcmp() is used, if all elements match
// a zero (false) is returned, +- value otherwise.
// Compares char arrays, bytes, ints, doubles

template<typename T>
int memcmp_E(const T* ramStart, int eeprom_start, size_t  len )
{
  int result = 0; // create and initialize return value
  uint8_t sample[sizeof(T)]; // allocate a buffer for EEPROM.get
  for (size_t i = 0; i < len * sizeof(T); i += sizeof(T)) {
    EEPROM.get( eeprom_start + i, sample); // copy EEPROM data out to RAM
    // then use memcmp to do the compare - avoids dealing with endianness
    result = memcmp ( ((uint8_t *) ramStart) + i, (uint8_t *) sample, sizeof(T) );
    if (result) break; // ie. non-zero
  }
  return result;
}

the output
memcmp_E_display.PNG

Thanks to @J-M-L and @westfw for helping!