Size of int is 4? I thought it was 2 bytes

was playing around with malloc and calloc and ran this

ptr = (int*)malloc(5 * sizeof(int));
int xx = 5 * sizeof(int);

Serial.print(xx);

you get 20

Also, what is the reason you have to request space a something * something. Why not just an number of bytes?

sevenoutpinball:
was playing around with malloc and calloc and ran this

ptr = (int*)malloc(5 * sizeof(int));
int xx = 5 * sizeof(int);

Serial.print(xx);

you get 20

Depends on the platform. Are you compiling for AVR? ARM? ESP?

Also, what is the reason you have to request space a something * something. Why not just an number of bytes?

You just found out the answer to that. You THOUGHT an int was 2 bytes, but you were wrong for the platform you were compiling for. If you had hardcoded malloc(10), you would not have gotten enough memory to store your 5 int variables.

currently I have an esp32 at the end of the wire. I will see what happens with an Uno.

Also I got an error msg when trying malloc(10)

sevenoutpinball:
Also I got an error msg when trying malloc(10)

I can't comment on that unless you share the code and the complete error message.

sevenoutpinball:
Also I got an error msg when trying malloc(10)

.. and we have to guess what it was?

I just tried a mkrzero and it isn't working, the port disappears as soon as the program loads.

As for that error, well now it isn't doing an error. Must have been a missed syntax thing.

The C function malloc has no place in 99.9% of C++ programs.
If you need a dynamic array of integers, use std::vector<int>. This is supported on all architectures supported by Arduino except AVR.

Using raw owning pointers and manual memory management using malloc/free or new/delete is bad practice and will inevitably lead to memory bugs (as you've discovered by crashing your mkrzero).
When available, use RAII wrappers like std::vector or std::unique_ptr/std::shared_ptr. If that's not an option because you don't have access to the standard library, the next step is to use a third-party library that provides similar containers or smart pointers.
Manually allocating memory should always be your last option. In that case, abstract it in a RAII class to prevent leaks, and always use new int[5], never use (int *)malloc(5 * sizeof(int)).

To answer the question in your title: the standard doesn't guarantee a fixed size for the int datatype. All it says guarantees is that it has a width of at least 16 bits. On AVR, it's usually 16 bits. On 32 and 64-bit platforms, it's usually 32 bits. But that's not a hard rule, it's platform-specific. If you need a fixed size, use the fixed width integer types like int32_t.

Pieter

oh wait, I do get an error

/Users/bb/BackupArduino/sketch_jan21b/sketch_jan21b.ino: In function 'void loop()':
sketch_jan21b:15:30: error: too few arguments to function 'void* calloc(size_t, size_t)'
ptr1 = (int*)calloc(bytenum);

however,

this works ok.

int bytenum=10000;
int ptr, ptr1;
ptr = (int
)malloc(bytenum * sizeof(int));
int xx = bytenum * sizeof(int);
Serial.print(xx);
ptr1 = (int
)calloc(bytenum, sizeof(int));

sevenoutpinball:
oh wait, I do get an error

/Users/bb/BackupArduino/sketch_jan21b/sketch_jan21b.ino: In function 'void loop()':
sketch_jan21b:15:30: error: too few arguments to function 'void* calloc(size_t, size_t)'
ptr1 = (int*)calloc(bytenum);

however,

this works ok.

int bytenum=10000;
int ptr, ptr1;
ptr = (int
)malloc(bytenum * sizeof(int));
int xx = bytenum * sizeof(int);
Serial.print(xx);
ptr1 = (int
)calloc(bytenum, sizeof(int));

The compiler is clearly telling your that calloc() takes two arguments.

Trying to reserve that much memory, I wouldn't attempt to dereference those pointers without checking for nullptr first.

PieterP:
The C function malloc has no place in 99.9% of C++ programs.
If you need a dynamic array of integers, use std::vector<int>. This is supported on all architectures supported by Arduino except AVR.

Using raw owning pointers and manual memory management using malloc/free or new/delete is bad practice and will inevitably lead to memory bugs (as you've discovered by crashing your mkrzero).
When available, use RAII wrappers like std::vector or std::unique_ptr/std::shared_ptr. If that's not an option because you don't have access to the standard library, the next step is to use a third-party library that provides similar containers or smart pointers.
Manually allocating memory should always be your last option. In that case, abstract it in a RAII class to prevent leaks, and always use new int[5], never use (int *)malloc(5 * sizeof(int)).

To answer the question in your title: the standard doesn't guarantee a fixed size for the int datatype. All it says guarantees is that it has a width of at least 16 bits. On AVR, it's usually 16 bits. On 32 and 64-bit platforms, it's usually 32 bits. But that's not a hard rule, it's platform-specific. If you need a fixed size, use the fixed width integer types like int32_t.

Pieter

Thank you Pieter for that excellent summary. I was putting together a news, weather, anything else I can think of gadget to send me emails of all that and thought gee, I wonder if this thing will clog itself up if nobody frees up the memory. That's why I thought about using it. These JSON arrays can take up 25k+ bytes and I wondered if repeatedly doing that would cause a problem.

PieterP:
Using raw owning pointers and manual memory management using malloc/free or new/delete is bad practice and will inevitably lead to memory bugs (as you've discovered by crashing your mkrzero).
When available, use RAII wrappers like std::vector or std::unique_ptr/std::shared_ptr.

Been thinking about it a while and I can't come up with a way to put a std::unique_ptr into a FreeRTOS Queue, or properly pull one out. The function prototypes are:

BaseType_t xQueueSendToBack( QueueHandle_t xQueue,
    const void * pvItemToQueue,
    TickType_t xTicksToWait );

BaseType_t xQueueReceive( QueueHandle_t xQueue,
    void * const pvBuffer,
    TickType_t xTicksToWait );

The object is copied into (and removed from) the queue via its address. Presumably that's a shallow copy. So, at the end of the process there will be two identical std::unique_ptr objects controlling the same resource -- the one in the queue pushing function and the one in the queue pulling function. Kind of defeats the purpose of a std::unique_ptr.

Anyone see a way around this?

Combining type-safe and memory-safe C++ code with C APIs like that is always tricky. std::unique_ptr is not TriviallyCopyable, so you're not allowed to use it with the FreeRTOS Queue functions, as those copy object representation, not the objects themselves.
Ideally, you'd have a Queue API that deals with C++ objects correctly, not just C data types. This would allow you to std::move stuff in and out of queues, calling the correct copy or move constructors, etc.

Since that's not something FreeRTOS supports out of the box, I think your best bet is to write your own wrappers with smart pointer overloads, and store raw pointers in the queue:

#include <memory>
#include <thread>
#include <freertos/queue.h>

template <class T> BaseType_t myQueueSendToBack(QueueHandle_t xQueue,
                                                std::unique_ptr<T> &&uptr,
                                                TickType_t xTicksToWait) {
  auto ptr = uptr.get();
  auto res = xQueueSendToBack(xQueue, static_cast<const void *>(&ptr), xTicksToWait);
  if (res == pdTRUE)
    uptr.release();
  return res;
}

template <class T> BaseType_t myQueueReceive(QueueHandle_t xQueue,
                                             std::unique_ptr<T> &uptr,
                                             TickType_t xTicksToWait) {
  T *ptr;
  auto res = xQueueReceive(xQueue, static_cast<void *>(&ptr), xTicksToWait);
  if (res == pdTRUE)
      uptr = std::unique_ptr<T>(ptr);
  return res;
}

struct Noisy {
  Noisy(int value) : value(value) { Serial.printf("Create  %d\r\n", value); }
  ~Noisy() { Serial.printf("Destroy %d\r\n", value); }
  int value;
};

QueueHandle_t q;

void taskA() {
  for (int i = 0; i < 10; ++i) {
    Serial.printf("\r\nProduce %d\r\n", i);
    std::unique_ptr<Noisy> msg{new Noisy{i}}; 
    // auto msg = std::make_unique<Noisy>(i);
    myQueueSendToBack(q, std::move(msg), portMAX_DELAY);
    vTaskDelay(1000 / portTICK_PERIOD_MS);
  }
}

void taskB() {
  for (int i = 0; i < 10; ++i) {
    std::unique_ptr<Noisy> msg;
    myQueueReceive(q, msg, portMAX_DELAY);
    Serial.printf("Consume %d\r\n", msg->value);
  }
}

void setup() {
  Serial.begin(115200);
  q = xQueueCreate(3, sizeof(void *));
  std::thread a{taskA};
  vTaskDelay(5000 / portTICK_PERIOD_MS); // wait a bit before consuming
  std::thread b{taskB};
  a.join(), b.join();
}

void loop() {}

You might notice I've edited my previous post. I'm not entirely sure what the best signature for the myQueueSendToBack function would be. The implementation now doesn't release the pointer if it wasn't added to the queue. By passing uptr as an rvalue reference you indicate that it will be moved from, but it also allows you to retry again if it failed to send since std::move doesn't actually do anything to the object, it just casts its argument to an xvalue (rvalue). By using an rvalue reference instead of an lvalue reference, you indicate to the caller that this function is expected to take ownership, so the caller has to call std::move explicitly. If you were to pass uptr by value, the storage would be released at the end of the function in all cases, even if sending to the queue failed or timed out.

PieterP explains some C++, and simultaneously demonstrates why people still use C-style malloc()

Sigh.

Thanks for the framework. I need to think about that some. In my application if xQueueSendToBack fails, I want to immediately de-allocate the resource being pointed to.

And this:

... std::move doesn't actually do anything to the object, it just casts its argument to an xvalue (rvalue).

But, doesn't casting / sending by rvalue and receiving by rvalue-reference invoke move semantics? So, upon return from myQueueSendToBack(), won't the caller's unique_ptr be pointing to nothing?

westfw:
PieterP explains some C++, and simultaneously demonstrates why people still use C-style malloc()

For my current application you're probably right. I could take the brain power necessary to create my own smart pointer wrapper and apply it to just handling dumb pointers properly.

Again, in my case it's really simple

  • If the sender's API call to place the (dumb) pointer in the queue fails, then the sender deletes the object being pointed to.

  • If the pointer get into the queue, the receiver pulls it out and processes it. Then, it deletes the object being pointed to.

No muss, no fuss, no memory leaks.

gfvalvo:
I need to think about that some. In my application if xQueueSendToBack fails, I want to immediately de-allocate the resource being pointed to.

In that case it's best to pass the unique pointer by value:

template <class T> BaseType_t myQueueSendToBack(QueueHandle_t xQueue,
                                                std::unique_ptr<T> uptr, // by value
                                                TickType_t xTicksToWait) {
  auto ptr = uptr.get(); // gets the pointer without releasing ownership
  auto res = xQueueSendToBack(xQueue, static_cast<const void *>(&ptr), xTicksToWait);
  if (res == pdTRUE)
    uptr.release(); // releases ownership without deallocating
  return res;
} // resource pointed to by uptr is deallocated if ownership was not released

gfvalvo:
But, doesn't casting / sending by rvalue and receiving by rvalue-reference invoke move semantics? So, upon return from myQueueSendToBack(), won't the caller's unique_ptr be pointing to nothing?

No, under the hood, passing by rvalue reference is the same as passing by lvalue reference or by pointer. The different semantics for rvalues come from the ability to overload functions, i.e. creating one function that accepts lvalues and another function that accepts rvalues.

For example:

struct SomeContainer {
  std::vector<int> vec;
  void setValue(const std::vector<int> &vec) {
    this->vec = vec; // invokes copy assignment of the std::vector class
  }
  void setValue(std::vector<int> &&vec) {
    this->vec = std::move(vec); // invokes move assignment of the std::vector class, the 
                                // actual "move" of the data happens here,
                                // no data is copied
  }
};
std::vector<int> v{1, 2, 3, 4}; // expensive to copy
SomeContainer c{};
c.setValue(v); // v is an lvalue, so the "const &" overload is selected, and contents are copied
c.setValue(std::move(v)); // std::move(v) is an xvalue, so the "&&" overload is selected

The actual moving of the storage of the vector doesn't happen when calling std::move nor at the instant of calling setValue, it happens inside of the setValue function, because of the move assignment. If you were to remove the line "this->vec = std::move(vec);" from the setValue function, nothing would be moved, and v could still be used at the call site after calling setValue.
The distinction between lvalue references and rvalue references is mainly there so you can differentiate between values that you're still using and values that you no longer need and whose resources can be reused.
The callee accepts an rvalue reference to signal to the caller that it will claim ownership of anything you pass to it, so it might steal its resources to reuse them.
The caller passes an rvalue so the correct rvalue reference overload is selected, and to indicate that it wants an overload that might claim overship of the argument if the callee wants to. This is done by either passing a temporary (prvalue), or by calling std::move on an lvalue (xvalue).

westfw:
PieterP explains some C++, and simultaneously demonstrates why people still use C-style malloc()

Sigh.

I only go into depth because I know gfvalvo has enough C++ experience.

The problem here is not that memory safety in C++ is harder than memory safety in C, quite the opposite is true. The problem is that there is no such thing as memory safety in C, so when using C APIs, you have to be careful and you might have to write some nontrivial wrappers.
Once you wrap such an API correctly (or rewrite it in C++ with ownership in mind), it is easier to use and harder to misuse than the C API. Then the user doesn't have to care about who's responsible for cleaning up resources, whether to do a deep or a shallow copy, etc.

IMHO, the solution is to promote memory- and type-safe APIs, not fall back to unsafe C practices.

And still ... no C++ in Linux kernel.
You'd think, with all the manifest advantages, someone would've convinced Torvalds.

Are there any popular/"Industry Standard" embedded multi-tasking kernels that have native C++ APIs ?

Symbian used to.
I wonder what happened there.