#include <unistd.h>
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
int integer;
char string[24];
} RECORD;
#define NRECORDS (100)
int main(void) {
RECORD record, *mapped;
int i, f;
FILE *fp;
fp = fopen("records.dat", "w+");
for (i=0; i<NRECORDS; i++) {
record.integer = i;
memset(record.string, 0, 24);
sprintf(record.string, "RECORD-%d", i);
fwrite(&record, sizeof(record), 1, fp);
}
fclose(fp);
fp = fopen("records.dat", "r+");
fseek(fp, 43*sizeof(record), SEEK_SET);
fread(&record, sizeof(record), 1, fp);
record.integer = 143;
memset(record.string, 0, 24);
sprintf(record.string, "RECORD-%d", record.integer);
fseek(fp, 43*sizeof(record), SEEK_SET);
fwrite(&record, sizeof(record), 1, fp);
fclose(fp);
f = open("records.dat", O_RDWR);
mapped = (RECORD *)mmap(0, NRECORDS*sizeof(record),
PROT_READ|PROT_WRITE, MAP_SHARED, f, 0);
mapped[43].integer = 243;
memset(record.string, 0, 24);
sprintf(mapped[43].string, "RECORD-%d", mapped[43].integer);
msync((void *)mapped, NRECORDS*sizeof(record), MS_ASYNC);
munmap((void *)mapped, NRECORDS*sizeof(record));
close(f);
exit(0);
}
mamset() "record" variable to 0 before sprintf() if you want consistent results.
RECORD record = { 0 };RECORD record;
...
record = (RECORD) { 0 };That code wasn't mine; it's taken directly from a Linux Programming book.
You have `= { 0 }` initializers for that purpose
Array and structure types are collectively called aggregate types.and
If there are fewer initializers in a brace-enclosed list than there are elements or members
of an aggregate, or fewer characters in a string literal used to initialize an array of known
size than there are elements in the array, the remainder of the aggregate shall be
initialized implicitly the same as objects that have static storage duration.
uint8_t fred[1000] = {0}; // I don't think this does anything other than fred[0]=0;There is no need to think, I quoted the standard that specifically says that the rest of the members would be initialized to 0 (as anything with static storage duration).
uint8_t fred[1000] = {1} places fred into DATA and sets up a block of 1000 bytes, all 0x01, in FLASH, to be copied to DATA at startup.No all 0x01, just the first one.
uint8_t fred[1000] = {1} does god knows what...The standard knows. Read it, it is good.
I am trying to work this out.
From my project notes:
With ARM GCC, statics are categorised thus:
int fred; goes into BSS (used to go into COMMON in GCC versions before v10)
int fred=0; goes into BSS (which by definition is zeroed by startup code)
int fred=1; goes into DATA (statics initialised to nonzero, copied to RAM at start)
It is guaranteed for all aggregate types (arrays and structures):QuoteArray and structure types are collectively called aggregate types.andQuoteIf there are fewer initializers in a brace-enclosed list than there are elements or members
of an aggregate, or fewer characters in a string literal used to initialize an array of known
size than there are elements in the array, the remainder of the aggregate shall be
initialized implicitly the same as objects that have static storage duration.
C23 introduces the empty initializer '{}', which initializes with zeroes without having to care about the type of the first item/field.
int a = { 0 }; /* This has always been allowed in standard C */For instance the standard is totally fine with the compiler putting a zero initialized value in .data instead of .bss, or it could put an object in bss and emit startup code to initialize it. These would both be unusual behavior and might violate platform specific guarantees, but the standard doesn't care.
C23 introduces the empty initializer '{}', which initializes with zeroes without having to care about the type of the first item/field.
Yes it is guaranteed, absolutely, unambiguously.
Um, this is misleading
Then one cannot implement anything because you need to know how the sections work in order to build a working product :)
So the standard is useless; you have to write some code with your chosen compiler and see where the variables ended up in the .map file.
unwillingness to actually read and understand the standard puts you at a significant disadvantage
So the standard is useless; you have to write some code with your chosen compiler and see where the variables ended up in the .map file.No, quite the opposite, if you have a standard compiler, then you have guaranteed behavior on any environment.
Which bits are wrong?None of this is wrong.
The environment setup you see in the reset handler is there to prepare the environment so that main() behaves as expected. You never have to think about where things went after main(). Embedded environment just puts some of the initializations on you, so you have to know a few details about your compiler. On a full OS you never have to see a linker script or startup code, your part starts from main().
int fred; goes into BSS (used to go into COMMON in GCC versions before v10)
The answer seems to be Yes.Yes, you can just use = {0}.
Right, but in embedded stuff, in order to meet the expectations of the compiler standards, the init code needs to be done right.Yes, but it is compiler creator's job to write a linker script for their compiler. You may choose to mess with it for your own needs, but then you are taking a job of a compiler writer and obviously need to have understanding of what compiler is doing. This is not a fault of the standard.
it is compiler creator's job to write a linker script for their compiler.
Linker scripts are in no way specified in the standard, so compiler may change their format or details at any point, it is on you to track those changes.
That's complete news to me. Where would this be found, for example, for GCC v10 as supplied with Cube IDE v1.13.1?The stock example file that comes with GCC itself is located here arm-gnu-toolchain\share\gcc-arm-none-eabi\samples\ldscripts (in the stock GCC, I have no idea how ST packages it).
There is a linkfile which comes with Cube but it is some hack which makes no reference to GCC.This is because "embedded" varies a lot, so vendors opt for making their own files. And you can make your own if you want. But you need to realize that this file would be specific to the compiler. IAR linker scripts and general memory model look nothing like this.
That seems to be a contradiction of the above :)How so? Linker scripts are implementation detail of a specific compiler. They don't even need to exist. It is possible to make a compiler that does not use them at all.
For such cases I write a custom memset but one cannot get the compiler to use that instead.Well, this is your problem. memset() is part of a C library that standard defines. Any low level removal of the libraries you do for optimization are up to you, but you are no longer in the standard territory.
Right, but in embedded stuff, in order to meet the expectations of the compiler standards, the init code needs to be done right.Yes, but it is compiler creator's job to write a linker script for their compiler. You may choose to mess with it for your own needs, but then you are taking a job of a compiler writer and obviously need to have understanding of what compiler is doing. This is not a fault of the standard.
In GCC v10:
uint8_t fred[1000]; // gets placed into BSS and thus filled with zeroes (I would have run memset() on that one, just in case)
uint8_t fred[1000] = {0}; // according to above, gets filled with zeroes (not sure if GCC v8 did that though)
For local (stack based) variables, I posted that above. The compiler generates explicit code which executes when the function is called. That bit if fairly obvious. but AIU
void function_joe (void
{
uint8_t fred[1000]; // does not get initialised
uint8_t fred[1000] = {0}; // gets zeroed with special code executed when the function is called (I would have run memset() on that one)
static uint8_t fred[1000]; // is placed into BSS (under a private name) and zeroed via BSS getting zeroed (I would have run memset() on that one)
static uint8_t fred[1000] = {0}; // same as above (I would have run memset() on that one)
}
Which bits are wrong?
I have already seen compilers which would place an explicitely initialized static variable, even with just a 0 initializer, in the 'data' section rather than 'bss'.
unless you're suddenly getting into dinosaur computing
declare them with no initializer
QuoteI have already seen compilers which would place an explicitely initialized static variable, even with just a 0 initializer, in the 'data' section rather than 'bss'.
That is dumb, surely, because what is the point of the concept of BSS? The DATA section is placed in FLASH and then copied over to RAM, so it is wasting a load of FLASH.
Quoteunless you're suddenly getting into dinosaur computing
Or have to work on an old product. One of my best selling boxes was done in 1995 :) In asm and Hitech H8/300 C. Still sells very well, no bugs found. It would be crazy to port it to a new environment. My current project is actually similar but with more features and ETH, USB, etc.
I am fairly sure that old compilers did not initialise them at all. I recall reading about that as a common criticism of C.
I am fairly sure that old compilers did not initialise them at all. I recall reading about that as a common criticism of C.
"The C programming language", 1st edition, from 1978 says on page 82:
"In the absence of explicit initialization, external and static variables are guaranteed to be initialized to zero; automatic and register variables have undefined (i.e., garbage) values."
["The C programming language", 1st edition is beside the point in this case, since in that version of the language it was explicitly prohibited to supply initializers when declaring automatic aggregates (same book, page 198). In K&R 1st edition automatic aggregates always began their lives fully uninitialized. No way around it. So, the matter of "full vs. partial" initialization of automatic aggregates simple did not exist at that time. (Meanwhile, for static aggregates zeroing-out beyond partial initializer was guaranteed by the 1st edition.)
$gcc -o mmap_eg mmap_eg.c
$./mmap_egStill seems odd to me that the desktop does this, but the laptop doesn't.
Classic symptom: your code appears to run fine in "debug mode" and starts behaving erratically when you switch to a "release mode".
I'm not sure I've seen a single compiler that would initialize locals in any mode.
Neither GCC or Clang do that for sure.
But both GCC and Clang warn about using uninitialized variables even at the lowest warning level.