choosing between relaxed and acquire/release memory ordering in atomics - c

I am implementing a very simple logic. Although, I am having a tough time figuring out the memory ordering to choose.
I have a simple struct like:
typedef unsigned int bitmap;
const size_t bits_count = sizeof(bitmap) * 8;
struct Container {
_Atomic(bitmap) reader_bitmap, writer_bitmap;
int cs[bits_count];
};
cs[bits_count] is the main deal here. Multiple producers will add data to it and multiple consumers will get data from it.
To synchronise this, I have 2 bitmaps, reader_bitmap and writer_bitmap. Both of them are initially 0. What I basically do is when I want to store a value to the array, I check the bitmap if it contains any unset bit and get that bit's position. Then I set the bit atomically and using this bit position as the index, I store data in cs[bit_posn]. Then to tell readers that data is available for reading, the same bit is set in reader_bitmap.
When some thread wants to get a value from this array, it will try to unset any one bit at bit_posn from reader_bitmap and get cs[bit_posn] and then it will set the same bit in writer_bitmap to tell the produces that this position is vacant for them to write.
Here is the code for the same (minimal reproducible example, I may have missed some edge cases):
#include <stdio.h>
#include <stdatomic.h>
#include <stdbool.h>
#define UNSET_BIT(bitmap, n) (bitmap & ~((typeof(bitmap))1 << n))
#define SET_BIT(bitmap, n) (bitmap | ((typeof(bitmap))1 << n))
typedef unsigned int bitmap;
const size_t bits_count = sizeof(bitmap) * 8;
struct Container {
_Atomic(bitmap) reader_bitmap, writer_bitmap;
int cs[bits_count];
};
bool store(struct Container *cont, int data) {
bitmap old_val, new_val;
int first_unset_bit;
old_val = atomic_load_explicit(&cont->writer_bitmap, memory_order_relaxed);
do {
first_unset_bit = __builtin_ffs(~old_val) - 1;
if (first_unset_bit == -1) return false;
new_val = SET_BIT(old_val, first_unset_bit);
} while(!atomic_compare_exchange_weak_explicit(
&cont->writer_bitmap,
&old_val,
new_val,
memory_order_relaxed,
memory_order_relaxed
));
cont->cs[first_unset_bit] = data;
old_val = atomic_load_explicit(&cont->reader_bitmap, memory_order_relaxed);
do {
new_val = SET_BIT(old_val, first_unset_bit);
} while(!atomic_compare_exchange_weak_explicit(
&cont->reader_bitmap,
&old_val,
new_val,
memory_order_relaxed,
memory_order_relaxed
));
return true;
}
int load(struct Container *cont, bool *did_load_fail) {
bitmap old_val, new_val;
int first_set_bit;
int retval;
*did_load_fail = false;
old_val = atomic_load_explicit(&cont->reader_bitmap, memory_order_relaxed);
do {
first_set_bit = __builtin_ffs(old_val) - 1;
if (first_set_bit == -1) {
*did_load_fail = true;
return -1;
}
new_val = UNSET_BIT(old_val, first_set_bit);
} while(!atomic_compare_exchange_weak_explicit(
&cont->reader_bitmap,
&old_val,
new_val,
memory_order_relaxed,
memory_order_relaxed
));
retval = cont->cs[first_set_bit];
old_val = atomic_load_explicit(&cont->writer_bitmap, memory_order_relaxed);
do {
new_val = UNSET_BIT(old_val, first_set_bit);
} while(!atomic_compare_exchange_weak_explicit(
&cont->writer_bitmap,
&old_val,
new_val,
memory_order_relaxed,
memory_order_relaxed
));
return retval;
}
int main(int argc, char *argv[])
{
return 0;
}
I tried to think about memory orderings here but it seems so vague to me. I need help understanding this in actual technical terms understanding what possible cases can actually happen.
I will try to give a general description on what my theory is. It maybe totally nonsense because I am really not sure how all this is actually working behind the scenes which is why I need help.
Let's say I chose to use release ordering everywhere like I did above. In store(), first I set the writer_bitmap's bit at bit_posn and then I set reader_bitmap at bit_posn.
Is it possible for some thread to "see" this bit_posn not set in writer_bitmap but set in reader_bitmap?
Let's say it was possible (actually want to know if it is or not), and then that thread called load(), and bit_posn was found set in reader_bitmap but that thread was still unaware that same bit is also set in writer_bitmap.
It would unset that in reader_bitmap and then try to unset the same in writer_bitmap. As per this, not knowing about writer_bitmap didn't matter as at last it was synchronized anyways.
Another case that I think of is writer_bitmap has a bit set but other thread doesn't know about it? (I guess this is not possible and is handled by atomics).
I know the question is pretty long. If it's unclear anywhere, please ask in the comments.

Related

memory access error when sorting struct field

i have some problems sorting a struct field and cannot find the answer why this is happening...
The Error message when i run the program says "Memory access error (German: Speicherzugriffsfehler (Speicherabzug geschrieben)). The error only comes whenever i try the proceed one of the following lines in the bubblesort:
x= *mannschaften[i];
*mannschaften[i]=*mannschaften[i+1];
*mannschaften[i+1]=x;
vertauscht=1;
I have the feeling that i might have to save memory with malloc() or calloc() at some point, but i don't know how to use this,yet...
Thank you for your help!
Best regards
kazimir
You can see the the entire code below:
#include <stdio.h>
#include <string.h>
typedef struct
{
char* nation;
int goals_scored;
int goals_conceded;
} Team;
typedef struct
{
Team* t1;
Team* t2;
int goals_t1;
int goals_t2;
}Spiel;
void ergebnis_eintragen(Spiel s)
{
s.t1->goals_scored = s.goals_t1;
s.t2->goals_scored = s.goals_t2;
s.t1->goals_conceded = s.goals_t2;
s.t2->goals_conceded = s.goals_t1;
}
void ausgabe (Team* mannschaften[8])
{
Team x;
int m=8;
int vertauscht;
do
{
vertauscht=0;
for (int i=0;i<8;i++)
{
if (mannschaften[i]->goals_scored<mannschaften[i+1]->goals_scored)
{
x= *mannschaften[i];
*mannschaften[i]=*mannschaften[i+1];
*mannschaften[i+1]=x;
vertauscht=1;
}
else if (mannschaften[i]->goals_scored==mannschaften[i+1]->goals_scored||mannschaften[i]->goals_conceded>mannschaften[i+1]->goals_conceded)
{
x = *mannschaften[i];
*mannschaften[i]=*mannschaften[i+1];
*mannschaften[i+1]=x;
vertauscht=1;
}
}
m-=1;
}
while (m>1 && vertauscht);
for (int i=0;i<8;i++)
printf("\nNation: %s\nTore: %d\nGegentore: %d\n",mannschaften[i]->nation,mannschaften[i]->goals_scored,mannschaften[i]->goals_conceded);
}
int main (void)
{
Team t1={"Kroatien",0,0};
Team t2={"Brasilien",0,0};
Team t3={"Niederlande",0,0};
Team t4={"Argentinien",0,0};
Team t5={"Marokko",0,0};
Team t6={"Portugal",0,0};
Team t7={"England",0,0};
Team t8={"Frankreich",0,0};
Spiel s1={&t1,&t2,5,3};
Spiel s2={&t3,&t4,5,6};
Spiel s3={&t5,&t6,1,0};
Spiel s4={&t7,&t8,1,2};
Spiel s5={&t4,&t1,3,0};
Spiel s6={&t8,&t5,2,0};
Spiel s7={&t1,&t5,2,1};
Spiel s8={&t4,&t8,7,5};
Team* ptr_team[8];
ptr_team[0] = &t1;
ptr_team[1] = &t2;
ptr_team[2] = &t3;
ptr_team[3] = &t4;
ptr_team[4] = &t5;
ptr_team[5] = &t6;
ptr_team[6] = &t7;
ptr_team[7] = &t8;
ergebnis_eintragen(s1);
ergebnis_eintragen(s2);
ergebnis_eintragen(s3);
ergebnis_eintragen(s4);
ergebnis_eintragen(s5);
ergebnis_eintragen(s6);
ergebnis_eintragen(s7);
ergebnis_eintragen(s8);
ausgabe(&ptr_team[0]);
}
I already tried looking for a simular question, but couldn't find anything...
In the for loop
for (int i=0;i<8;i++)
{
if (mannschaften[i]->goals_scored<mannschaften[i+1]->goals_scored)
when i is equal to 7 then expressions like this
mannschaften[i+1]->goals_scored)
access memory outside the array ptr_team defined in main.
You could write your loop like for example
for (int i=1;i<8;i++)
{
if (mannschaften[i-1]->goals_scored<mannschaften[i]->goals_scored)
//...
Also it seems in this if statement
else if (mannschaften[i]->goals_scored==mannschaften[i+1]->goals_scored||mannschaften[i]->goals_conceded>mannschaften[i+1]->goals_conceded)
you mean the logical operator && instead of the logical operator ||
else if (mannschaften[i]->goals_scored==mannschaften[i+1]->goals_scored && mannschaften[i]->goals_conceded>mannschaften[i+1]->goals_conceded)
Using the variable m as is in the do-while loop does not make a great sense. It is enough to use the variable vertauscht
do
{
//...
} while ( vertauscht);
And it is a bad idea to use magic numbers like 8.
You could declare your function like
void ausgabe (Team* mannschaften[], size_t n );
and call it at least like
ausgabe( ptr_team, 8 );
Then within the function you should use the variable n instead of the magic number 8 as for example
for ( size_t i = 1;i < n;i++ )
Otherwise you will need to change your function each time when the size of the passed array will be changed.
You should always write a more general function.
Pay attention to that your program will look much better and will be more readable if you will use English words for identifiers.

Reading and writing in-memory data

In the following example, I am taking a struct that occupies 32 bytes in memory and writing that to a file and reading it back in -- i.e., serializing the data to a binary format:
#include <stdio.h>
typedef struct _Person {
char name[20];
int age;
double weight;
} Person;
int main(void)
{
Person tom = (Person) {.name="Tom", .age=20, .weight=125.0};
// write the struct to a binary file
FILE *fout = fopen("person.b", "wb");
fwrite(&tom, sizeof tom, 1, fout);
fclose(fout);
// read the binary data and set the person to that
Person unknown;
FILE *fin = fopen("person.b", "rb");
fread(&unknown, sizeof unknown, 1, fin);
fclose(fin);
// confirm all looks ok
printf("{name=%s, age=%d, weight=%f}", unknown.name, unknown.age, unknown.weight);
}
Note however that these are all values on the stack, and no pointers/indirection is involved. How might data be serialized to a file when, for example, multiple pointers can be involved, multiple variables may point to the same memory location, etc. Is this effectively what protocol buffers do?
Ok so you want a binary file. I used to do it this way long ago. It's fine. It just breaks when you move to another platform or bitness. I'm teaching the old way because it's a good place to start. Newer ways are popular now because they keep working when changing platforms or bitnesses.
When writing records to the file I would use structs like this:
typedef struct _Person {
char name[20];
int age;
double weight;
} Person;
typedef struct _Thing {
char name[20];
};
typedef struct _Owner {
int personId;
int thingId;
} Owner;
See how the Owner structure has Id members. These are just indices into the arrays of the other structures.
These can be written out to a file one after another, usually prefixed by a single integer written directly that says how many records of each kind. The reader just allocates an array of structs with malloc big enough to hold them. As we add more items in memory we resize the arrays with realloc. We can (and should) also mark for deleting (say by setting the first character of name to 0) and reusing the record later.
The writer looks something like this:
void writeall(FILE *h, Person *allPeople, int nPeople, Thing *allThings, int nThings, Owner *allOwners, int nOwners)
{
// Error checking omitted for brevity
fwrite(&nPeople, sizeof(nPeople), 1, h);
fwrite(allPeople, sizeof(*allPeople), nPeople, h);
fwrite(&nThings, sizeof(nThings), 1, h);
fwrite(allThings, sizeof(*allThings), nThings, h);
fwrite(&nOwners, sizeof(nOwners), 1, h);
fwrite(allOwners, sizeof(*allOwners), nOwners, h);
}
The reader in turn looks like this:
int writeall(FILE *h, Person **allPeople, int *nPeople, int *aPeople, Thing **allThings, int *nThings, int *aThings, Owner **allOwners, int *nOwners, int *aOwners)
{
*aPeople = 0; // Don't crash on bad read
*aThigns = 0;
*aOwners = 0;
*allPeople = NULL;
*allThings = NULL;
*allOwners = NULL;
if (1 != fread(nPeople, sizeof(*nPeople), 1, h)) return 0;
*allPeople = malloc(sizeof(**allPeople) * *nPeople);
if (!allPeople) return 0; // OOM
*aPeople = *nPeople;
if (*nPeople != fread(*allPeople, sizeof(**allPeople), nPeople, h)) return 0;
if (1 != fread(nThings, sizeof(*nThings), 1, h)) return 0;
*allThings = malloc(sizeof(**allThings) * *nThings);
if (!allThings) return 0; // OOM
*aThings = *nThings;
if (*nThings != fread(*allThings, sizeof(**allThings), nThings, h)) return 0;
if (1 != fread(nOwners, sizeof(*nOwners), 1, h)) return 0;
*allOwners = malloc(sizeof(**allOwners) * *nOwners);
if (!allOwners) return 0; // OOM
*aOwners = *nOwners;
if (*nOwners != fread(*allOwners, sizeof(**allOwners), nOwners, h)) return 0;
return 1;
}
There was an old technique for writing a heap arena directly to disk and reading it back again. I recommend never using it, and never storing pointers on disk. That way lies security nightmares.
When memory was cheap I would have talked about how to use block allocation and linked blocks to dynamically update the on-disk records partially; but now for problems you're going to encounter at this level I say don't bother, and just read the whole thing into RAM and write it back out again. Eventually you will learn databases, which handles that stuff for you.

How to create derived structure properties in C

In Python, its possible to create a derived property from a class using the #property decorator for example
class State():
def __init__(self, fav_num_monday, fav_num_not_monday, is_monday):
self.fav_num_monday = fav_num_monday
self.fav_num_not_monday = fav_num_not_monday
self.is_monday = is_monday
#property
def fav_num(self):
return self.is_monday * self.fav_num_monday + \
(1 - self.is_monday) * self.fav_num_not_monday
state = State(12, 5, 0)
print("Current favourite number: %d" % state.fav_num)
My question is then what is the best way to achieve this in C (where speed is of the utmost importance). I've have added below some ways I have tried but am not sure if they could have repercussions in a larger codebase. They are as follows:
Simply writing out the whole expression each time.
Pros: No unexpected repercussions, no code/speed penalty.
Cons: Ugly code, takes a long time to write.
Using a get function.
Pros: Code easier to read.
Cons: Inefficient code (slower than 1).
Defining a macro.
Pros: No code/speed penalty. Code quick to write.
Cons: Potential repercussions later, code not so easy to follow.
The example program is below
#include <stdio.h>
#include <string.h>
#define state_fav_num state.is_monday * state.fav_num_monday + (1 - state.is_monday) * state.fav_num_not_monday
struct State {
int fav_num_monday;
int fav_num_not_monday;
int is_monday;
};
int get_state(struct State *state, char *property) {
// Returns value of the property in state.
// Allows us to create derived properties also.
if (!strncmp(property, "fav_num_monday", 14)) {
return state->fav_num_monday;
} else if (!strncmp(property, "fav_num_not_monday", 18)) {
return state->fav_num_not_monday;
} else if (!strncmp(property, "is_monday", 9)) {
return state->is_monday;
} else if (!strncmp(property, "fav_num", 7)) {
return state->is_monday * state->fav_num_monday +
(1 - state->is_monday) * state->fav_num_not_monday;
}
}
int main() {
// Set the state.
struct State state;
state.fav_num_monday = 12;
state.fav_num_not_monday = 5;
state.is_monday = 1;
// Print favourite number in different ways.
printf("\n1) Current favourite number is %d.",
state.is_monday * state.fav_num_monday +
(1 - state.is_monday) * state.fav_num_not_monday);
printf("\n2) Current favourite number is %d.",
get_state(&state, "fav_num"));
printf("\n3) Current favourite number is %d.",
state_fav_num);
printf("\n");
return 0;
}
You can get the best of both worlds (function and macro) for readability and performance, with a static inline function.
You usually wouldn't use that, but if you know the compiler is going to optimize its code away, then it's OK to use it. The usual rule I use is 3 or less lines of code, and the function should require extra performance.
That said, your get_state doesn't meet the (my) requirements for a static inline function, but if you only want a function to get only the fav_num, it would make sense:
struct State {
int fav_num_monday;
int fav_num_not_monday;
bool is_monday;
};
static inline int get_fav_num(const struct State *state)
{
if (state->is_monday)
return state->fav_num_monday;
else
return state->fav_num_not_monday;
}
int main(void)
{
struct State state;
int fav_num;
state = (struct State){
.fav_num_monday = 12;
.fav_num_not_monday = 5;
.is_monday = 1;
};
// Print favourite number in different ways.
printf("\n");
if (state.is_monday)
fav_num = state->fav_num_monday;
else
fav_num = state->fav_num_not_monday;
printf("1) Current favourite number is %d.\n", fav_num);
fav_num = get_fav_num(&state);
printf("4) Current favourite number is %d.\n", fav_num);
return 0;
}
Disclaimer: This code needs C99 or later.
Although here the code is all together, the struct State {...}; and the static inline function would usually go in a header .h file.
Also, I would improve your get_state function in this way:
enum Properties {
FAV_NUM_MONDAY,
FAV_NUM_NOT_MONDAY,
IS_MONDAY,
FAV_NUM
};
int get_state(const struct State *state, int property)
{
switch (property) {
case FAV_NUM_MONDAY:
return state->fav_num_monday;
case FAV_NUM_NOT_MONDAY:
return state->fav_num_not_monday;
case IS_MONDAY:
return state->is_monday;
case FAV_NUM:
return get_fav_num(state);
default:
return -1; /* Error */
}
}
This function would be a usual extern function and would go in a .c file, although the enum Properties should go in a header file so that it can be used by the user of the function.
Edit: Add high performance version using array
state.h
#include <stdint.h>
enum State_Properties {
FAV_NUM_MONDAY,
FAV_NUM_NOT_MONDAY,
IS_MONDAY,
STATE_PROPERTIES
};
static inline
uint_fast8_t get_fav_num(const uint_fast8_t *restrict (state[STATE_PROPERTIES]))
{
if ((*state)[IS_MONDAY])
return (*state)[FAV_NUM_MONDAY];
else
return (*state)[FAV_NUM_NOT_MONDAY];
}
main.c
#include <inttypes.h>
#include "state.h"
int main(void)
{
uint_fast8_t state[STATE_PROPERTIES];
uint_fast8_t fav_num;
uint_fast8_t fav_num_monday;
state = (uint_fast8_t [STATE_PROPERTIES]){
[FAV_NUM_MONDAY] = 12;
[FAV_NUM_NOT_MONDAY] = 5;
[IS_MONDAY] = true;
};
// Print favourite number in different ways.
fav_num = get_fav_num(&state);
printf("5) Current favourite number is %"PRIuFAST8".\n", fav_num);
// Example of how to retrieve any property:
fav_num_monday = state[FAV_NUM_MONDAY];
}
Of course you can change the type to anyone you want. I used uint_fast8_t, because your data can fit in there, and it is the fastest type on any system.

Timing behavior of null pointers in C

The below is mostly tested for Microsoft CL Version 17.00.50727.1 on Windows 7, but I see something similar with g++. I'm quite sure that the logical function is correct. The question is only about the timing.
Essentially I have a function that dynamically returns new "blocks" of data as needed. If it runs out of space in its "page", it makes a new page.
The purpose of the blocks is to match incoming data keys. If the key is found, that's great. If not, then a new data key is added to the block. If the block runs out of space, then a new block is created and linked to the old one.
The code works whether or not the block-making function explicitly sets the "next" pointer in the new block to NULL. The theory is that calloc() has set the content to 0 already.
The first odd thing is that the block-making function takes about 5 times(!) longer to run when that "next" pointer is explicitly set to NULL. However, then that is done, then the timing of the overall example behaves as expected: It takes linearly longer to match a new key, the more entries there are in the key list. The only difference occurs when a key is added which causes a new block to be fetched. The overhead of doing that is similar to the time taken to call the block-making function.
The only problem with this is that the block-making function is unacceptably slow then.
When the pointer is NOT explicitly set to NULL, then the block-making function becomes nice and fast -- maybe half to a quarter of the key-matching function, instead of as long or even longer.
But then the key-matching function starts to exhibit odd timing behavior. It does mostly increase linearly with the number of keys. It still has jumps at 16 and 32 keys (since the list length is 16). But it also has a large jump at key number 0, and it has large jumps at keys number 17, 33 etc.
These are the key numbers when the program first has to look at the "next" pointer. Apparently it takes a long time to figure out that the 0 value from calloc is really a NULL pointer? Once it knows this, the next times are faster.
The second weird thing is that the effect goes away if the data struct consists exclusively of the key. Now the jumps at 0, 17, 33 etc. go away whether or not the "next" pointer is explicitly set to NULL. But when "int unused[4]" is also in the struct, then the effect returns.
Maybe the compiler (with option /O2 or with -O3 for g++) optimizes away the struct when it consists of a single number? But I still don't see why that would affect the timing behavior in this way.
I've tried to simplify the example as much as I could from the real code, but I'm sorry that it's still quite long. It's not that complicated, though.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>
void timer_start(int n);
void timer_end(int n);
void print_times();
// There are pages of blocks, and data entries per block.
// We don't know ahead of time how many there will be.
// GetNextBlock() returns new blocks, and if necessary
// makes new pages. MatchOrStore() goes through data in
// a block to try to match the key. It won't ever match
// in this example, so the function makes a new data entry.
struct dataType
{
// Surprise number 1: If the line with "unused" is
// commented out, things behave as expected, even if
// Surprise number 2 is in effect.
int unused[4];
int key;
};
#define DATA_PER_BLOCK 16
struct blockType
{
char nextEntryNo;
struct dataType list[DATA_PER_BLOCK];
struct blockType * next;
};
struct pageType
{
int nextBlockNo;
struct blockType * list;
struct pageType * next;
struct pageType * prev;
};
struct blockType * GetNextBlock();
void MatchOrStore(
struct dataType * dp,
struct blockType * bp);
struct pageType * pagep;
int main(int argc, char * argv[])
{
pagep = (struct pageType *) 0;
struct dataType data;
for (int j = 0; j < 50000; j++)
{
struct blockType * blockp = GetNextBlock();
// Make different keys each time.
for (data.key = 0; data.key < 40; data.key++)
{
// One timer per key number, useful for statistics.
timer_start(data.key);
MatchOrStore(&data, blockp);
timer_end(data.key);
}
}
print_times();
exit(0);
}
#define BLOCKS_PER_PAGE 5000
struct blockType * GetNextBlock()
{
if (pagep == NULL ||
pagep->nextBlockNo == BLOCKS_PER_PAGE)
{
// If this runs out of page space, it makes some more.
struct pageType * newpagep = (struct pageType *)
calloc(1, sizeof(struct pageType));
newpagep->list = (struct blockType *)
calloc(BLOCKS_PER_PAGE, sizeof(struct blockType));
// I never actually free this, but you get the idea.
newpagep->nextBlockNo = 0;
newpagep->next = NULL;
newpagep->prev = pagep;
if (pagep)
pagep->next = newpagep;
pagep = newpagep;
}
struct blockType * bp = &pagep->list[ pagep->nextBlockNo++ ];
// Surprise number 2: If this line is active, then the
// timing behaves as expected. If it is commented out,
// then presumably calloc still sets next to NULL.
// But the timing changes in an unexpected way.
// bp->next = (struct blockType *) 0;
return bp;
}
void MatchOrStore(
struct dataType * dp,
struct blockType * blockp)
{
struct blockType * bp = blockp;
while (1)
{
for (int i = 0; i < bp->nextEntryNo; i++)
{
// This will spend some time traversing the list of
// blocks, failing to find the key, because that's
// the way I've set up the data for this example.
if (bp->list[i].key != dp->key) continue;
// It will never match.
return;
}
if (! bp->next) break;
bp = bp->next;
}
if (bp->nextEntryNo == DATA_PER_BLOCK)
{
// Once in a while it will run out of space, so it
// will make a new block and add it to the list.
timer_start(99);
struct blockType * bptemp = GetNextBlock();
bp->next = bptemp;
bp = bptemp;
timer_end(99);
}
// Since it didn't find the key, it will store the key
// in the list here.
bp->list[ bp->nextEntryNo++ ].key = dp->key;
}
#define NUM_TIMERS 100
#ifdef _WIN32
#include <time.h>
LARGE_INTEGER
tu0[NUM_TIMERS],
tu1[NUM_TIMERS];
#else
#include <sys/time.h>
struct timeval
tu0[NUM_TIMERS],
tu1[NUM_TIMERS];
#endif
int ctu[NUM_TIMERS],
number[NUM_TIMERS];
void timer_start(int no)
{
number[no]++;
#ifdef _WIN32
QueryPerformanceCounter(&tu0[no]);
#else
gettimeofday(&tu0[no], NULL);
#endif
}
void timer_end(int no)
{
#ifdef _WIN32
QueryPerformanceCounter(&tu1[no]);
ctu[no] += (tu1[no].QuadPart - tu0[no].QuadPart);
#else
gettimeofday(&tu1[no], NULL);
ctu[no] += 1000000 * (tu1[no].tv_sec - tu0[no].tv_sec )
+ (tu1[no].tv_usec - tu0[no].tv_usec);
#endif
}
void print_times()
{
printf("%5s %10s %10s %8s\n",
"n", "Number", "User ticks", "Avg");
for (int n = 0; n < NUM_TIMERS; n++)
{
if (number[n] == 0)
continue;
printf("%5d %10d %10d %8.2f\n",
n,
number[n],
ctu[n],
ctu[n] / (double) number[n]);
}
}

Can't understand this conversion from C to Assembly

I'd like to know if someone can explain me the solution to this problem:
the code is:
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int c[20];
int n;
} t_coda;
t_coda coda;
void init(t_coda *coda) {
coda->n = 0;
}
void add(t_coda *coda, int x) {
if (coda->n < 20)
coda->c[(coda->n)++] = x;
}
main() {
init(&coda);
coda->n=1;
coda->c[0]=2;
add(&coda,3);
add(&coda,4);
}
And I need to know the corresponding instruction of: coda->n = 0; and coda->c[(coda->n)++] = x; in simplesem (an assembly-like semantic);
The solution is:
set D[D[0]+3]+20, 0
for the first question
and:
set D[D[0]+3]+D[D[D[0]+3]+20], D[D[0]+4]
set D[D[0]+3]+20, D[D[D[0]+3]+20] + 1
for the second one;
D is the Data stack, and D[0] return the value contained in the 0-cell of the data
Thank you
I would guess that...
D[0]+3 is a reference to the address of coda (the *coda in the function call)
D[D[0]+3] is a lookup of the data at the address where coda is stored
D[D[0]+3]+20 is an offset of 20 from where coda begins, thus moving past coda->c (which is 20 items) to get to coda->n.
That should help you to understand the first one; the same ideas can be extended to the second.

Resources