Reference counting

K

Keith Thompson

Tim Rentsch said:
Keith Thompson said:
Angel said:
For hashing, is the object representation of a 'void *' guaranteed to be
the same any time a 'void *' points to the same object? Is it likely?
(I think so.) :)

The standard only says pointers are represented in an
implementation-defined manner. But as long as the pointer value doesn't
change, I doubt the representation will. On most implementations, a
pointer is really just a 32- or 64-bit unsigned number, indicating a
certain spot in virtual memory.

Quibble: Pointer representation is unspecified, not
implementation-defined. [snip]

How do you reconcile this assertion with 6.2.6.1p2? Certainly
it looks like the representation of any pointer value stored in
an object would be (at least) implementation-defined.

Quite easily: by failing to read it carefully enough. :cool:}

But now that I have, I'm confused. Here's what it says:

1 The representations of all types are unspecified except as
stated in this subclause.

2 Except for bit-fields, objects are composed of contiguous
sequences of one or more bytes, the number, order, and
encoding of which are either explicitly specified or
implementation-defined.

3 Values stored in unsigned bit-fields and objects of type unsigned
char shall be represented using a pure binary notation.

As far as I can tell, *all* types have implementation-defined
representations; the section says so for non bit-fields and for
unsigned bit-fields, and I don't think there's enough wiggle room
to say that signed bit-fields have unspecified representations.

So why does paragraph 1 say "unspecified" rather than
"implementation-defined"? "(Unspecified" is a subset of
"implementation-defined", so it's not wrong, but it seems needlessly
unspecific.
 
L

luser- -droog

luser- -droog said:
An external table would avoid all the alignment-guessing for pre-C1X.
I'm too lazy to finish coding this, but what about a simple open-
addressed
hash-table?

Laziness overcome!

517(0)10:47 PM:~ 0> cat rfct.c
#define TEST

enum { MAX = 100, TABSZ = 2*MAX+1 };

#include <stdlib.h>

struct ref_ {
void *ptr;
int count;
void (*final)(void *ptr);
} typedef ref_;

ref_ ref_tab_[TABSZ];

void ref_init(void) {
int i;
for (i=0; i < TABSZ; i++) {
ref_tab_ = (ref_){ .ptr = NULL, .count = 0, .final =
NULL };
}
}

/* courtesy of Shao Miller */
unsigned char ptr_hash(void * ptr) {
unsigned char
* pos = (void *)&ptr,
* end = pos + sizeof ptr,
result = 0;
while (pos < end)
result ^= *pos++;
return result;
}

/* pete says UB when 'sizeof (int) == 1' */
/* static xxx ptr_hash_table[1 << CHAR_BIT]; */

ref_ *ref_find(void *ptr) {
int
h = ptr_hash(ptr) % TABSZ,
i = h;
if (ptr == ref_tab_.ptr || ref_tab_.ptr == NULL)
return &ref_tab_;
for (++i; i < TABSZ; i++) {
if (ptr == ref_tab_.ptr || ref_tab_.ptr == NULL)
return &ref_tab_;
}
for (i = 0; i < h; i++) {
if (ptr == ref_tab_.ptr || ref_tab_.ptr == NULL)
return &ref_tab_;
}
return NULL;
}

void ref_new(void *ptr) {
ref_ *ref = ref_find(ptr);
if (ref) {
ref->ptr = ptr;
ref->count = 1;
ref->final = free;
}
}

ref_ *ref_get(void *ptr) {
ref_ *ref = ref_find(ptr);
if (ref)
if (ref->ptr == NULL)
return NULL;
return ref;
}

void *ref_malloc(size_t sz) {
void *ptr = malloc(sz);
if (ptr)
ref_new(ptr);
return ptr;
}

void ref_inc(void *ptr) {
ref_ *ref = ref_get(ptr);
if (ref) ref->count++;
}

void ref_dec(void *ptr) {
ref_ *ref = ref_get(ptr);
if (ref)
if (--ref->count == 0) {
ref->final(ptr);
ref->ptr = 0;
ref->final = NULL;
}
}

void ref_set_final(void *ptr, void (*final)(void *ptr)) {
ref_ *ref = ref_get(ptr);
if (ref)
ref->final = final;
}

#ifdef TEST
#include <string.h>
#include <stdio.h>

#define strdup(s) strcpy(ref_malloc(sizeof(s)),s)

void print(char *s) {
ref_inc(s);
if (ref_get(s)) printf("%s\n", s);
ref_dec(s);
}

int main(void) {
ref_init();
char *s = strdup("apple");
char *t = strdup("banana");

print(s); /* apple */
print(t); /* banana */

ref_dec(s);
print(s); /* */
print(t); /* banana */

ref_dec(t);
print(s); /* */
print(t); /* */

return 0;
}

#endif
518(0)10:47 PM:~ 0> make rfct && rfct
cc -g -Wall -c -o rfct.o rfct.c
cc rfct.o -o rfct
apple
banana
banana
519(0)10:47 PM:~ 0>
 
S

Shao Miller

Seems reasonable. For example, how much do you trust users of
sprintf?

Indeed.


Can you elaborate?

If you have:

struct {
int x;
} foo;

struct {
int x;
} bar;

I believe that you are not guaranteed that 'sizeof foo == sizeof bar',
as they have different types... Though I find it highly unlikely.
Similarly, the 'ALIGNOF' macro above could yield different results upon
invocations with the same 'type' because the implementation could have
generated different padding for the different types of "helper" structs.
It's a cost benefit analysis. We're not necessarily limited to using
one single method. A simpler interface can use a conservative
alignment valid for any type to make it easier for the user (assuming
one can determine ALIGN_MAX with enough confidence through platform
and compiler documentation, autoconf, or some C offsetof
incantation). If more performance needs to be squeaked out, a more
complex interface may be used. Personally, I just haven't been in a
situation that's motivated me to think of using reference counting.

Well, assuming the 'ALIGNOF' above is a pretty good alignment guess, the
code example below makes use of it to allocate an object with a
reference-counting prefix object. It doesn't have:
- 'sprintf' user concerns ;)
- the overhead of tracking an object's originally-allocated size

I'm not sure what disadvantages there are to the approach in the code
below. Any ideas? (Or anyone else?)
The design of malloc is predicated on the user not needing to track
the size of the allocation. Otherwise C would be designed with
'free( void* p, size_t sz )'. Of course, malloc is responsible to
track allocation size internally, but it's in the implementation
space, not user space. For better or worse, there is no standard
method to take a raw pointer and obtain its allocation size. There
are times when knowing the allocation space is desirable: designing a
custom allocator, managing an auto-resizing container like a string or
array, when its the destination buffer of a snprintf function, etc,
but certainly not universal.

I agree that it's a burden to pass around a size, but was just
commenting on the sanity of having it handy.
That is in the realm of debugging allocators, which is good practice
to use. Delegating this responsibility directly to users is in my
opinion a bad idea.

Hmmm... Yes I guess it's automation and burden-relief versus
encouraging awareness of every detail.
Most debugging allocators use the preprocessor to replace instances of
'malloc' with their own special call to allocator. By providing your
own malloc wrapper, one can create an interface to allocate a
reference counted pointer through something like 'rc_malloc', but
still tap into dmalloc or valgrind to verify that your implementation
doesn't trash the address space, i.e.

---dmalloc.h---

#define malloc(size) \
dmalloc_malloc(__FILE__, __LINE__, (size), DMALLOC_FUNC_MALLOC, 0,
0)

Again, this is just one path from several viable methods to implement
reference counting. I'm not arguing for superiority of one method
over another simply because I have no real-use experience with
reference counting, just curiosity.

Well unfortunately, the 'pfxalloc' function below is not a drop-in
replacement for 'malloc', but a wrapper function or macro or both could
potentially produce a drop-in replacement.

Here's the code, which ought to compile as a single file, or one can
split it up into multiple files. Also available, with nice colours, at:

http://codepad.org/0R6QzND9

-----

/**
* Delete all lines containing
* 'ALL_IN_ONE' if you split this example
* into separate files.
*/
#define ALL_IN_ONE 1


/****
* pfxalloc.h
*
* Shao Miller, 2011
* It took several minutes to find
* a free *alloc. (No pun intended.)
*/
#ifndef PFXALLOC_H_

/* For 'offsetof' and 'size_t' */
#include <stddef.h>

/*** Macros */
#define PFXALLOC_H_

#ifndef alignof

/* Derived from Mr. Chris M. Thomasson */
#define alignof(type) \
(offsetof(struct {char c; type t;}, t))

#ifdef __alignof_is_defined
#undef __alignof_is_defined
#endif /* __alignof_is_defined */

#define __alignof_is_defined 1
#endif /* alignof */

/*** Functions */

/**
* Allocate memory for an object and an
* associated "prefix" object.
*
* @v pfx_size The "prefix" size.
* @v obj_alignment The object's alignment
* requirement.
* @v obj_size The object's size.
* @ret void * Points to the object,
* or is NULL for failure.
*/
extern void * pfxalloc(size_t, size_t, size_t);

/**
* Get an object's associated "prefix" object.
*
* @v obj The object to fetch the
* associated "prefix" for.
* @ret void * Points to the "prefix",
* or is NULL for failure.
*/
extern void * pfxget(void *);

#endif /* PFXALLOC_H_ */


/**** pfxalloc.c */
/* Shao Miller, 2011 */

/* For 'malloc' */
#include <stdlib.h>
#if !ALL_IN_ONE
/* For 'size_t', 'ptrdiff_t' and 'NULL' */
#include <stddef.h>
/* For 'alignof' */
#include "pfxalloc.h"
#endif /* !ALL_IN_ONE */

/*** Constants */
enum cv {
cv_ptrdiff_alignment =
alignof(ptrdiff_t),
cv_zero = 0
};

/*** Functions */

/**
* The layout looks like:
* +-----+-------------+--------+--------+
* | pfx | opt_padding | offset | |
* +- -+- -+- -+ object |
* | header | |
* +----------------------------+--------+
*/
void * pfxalloc(
size_t pfx_size,
size_t obj_alignment,
size_t obj_size
) {
size_t offset_at;
ptrdiff_t * offset;
size_t header_size;
size_t total_size;
unsigned char * mem;
unsigned char * obj;

/* Sanity-check alignment value */
if (obj_alignment & (obj_alignment - 1))
return NULL;

/* Calculate the offset position */
offset_at = pfx_size;
offset_at += sizeof *offset;
/* Check for wrap-around */
if (offset_at < pfx_size)
return NULL;
--offset_at;
offset_at /= sizeof *offset;

/* Calculate the header size */
header_size = (offset_at + 1) * sizeof *offset;
/* Check for wrap-around */
if (header_size < pfx_size)
return NULL;

/* Calculate padding */
if (obj_alignment > cv_ptrdiff_alignment) {
size_t new_hdr_size = header_size;
new_hdr_size += obj_alignment;
/* Check for wrap-around */
if (new_hdr_size < header_size)
return NULL;
--new_hdr_size;
new_hdr_size /= obj_alignment;
new_hdr_size *= obj_alignment;
header_size = new_hdr_size;
}

/* Allocate storage */
total_size = header_size + obj_size;
/* Check for wrap-around */
if (total_size < pfx_size || total_size < obj_size)
return NULL;
mem = malloc(total_size);
if (!mem)
return mem;

/* Point to the object */
obj = mem + header_size;

/* Note the offset to the prefix */
offset = (ptrdiff_t *) obj - 1;
*offset = obj - mem;

return obj;
}

/* Fetch an asociated prefix for an object */
void * pfxget(void * obj) {
ptrdiff_t * offset;
unsigned char * prefix;

if (!obj)
return obj;

offset = obj;
--offset;
prefix = obj;
prefix -= *offset;

return prefix;
}


/**** ref_cnt.h */
/* Shao Miller, 2011 */
#ifndef REF_CNT_H_

#if !ALL_IN_ONE
/* For 'size_t' */
#include <stddef.h>
#include "align.h"
#endif /* !ALL_IN_ONE */

/*** Macros */
#define REF_CNT_H_

/*** Function types */
typedef void f_ref_cnt_free(void *);

/*** Functions */

/**
* Create a new, reference-counted object
*
* @v size The size of the object
* to allocate, in bytes.
* @v alignment The alignment requirement
* for the object.
* @v dealloc The de-allocation function
* to call, when refs == 0.
* @ret void * Points to the newly-
* allocated object, or is
* NULL, upon failure.
*/
extern void * NewRefCountedObj(
size_t,
size_t,
f_ref_cnt_free *
);

/**
* Increment the reference count for an object.
*
* @v obj The object to reference.
* @ret void * Points to the object, or is
* NULL if too many references.
*/
extern void * GetRef(void *);

/**
* Decrement the reference count for an object.
*
* @v obj The object to dereference.
*/
extern void PutRef(void *);

#endif /* REF_CNT_H_ */


/**** ref_cnt.c */
/* Shao Miller, 2011 */

#if !ALL_IN_ONE
/* For 'size_t' */
#include <stddef.h>
#include "pfxalloc.h"
#include "ref_cnt.h"
#endif /* !ALL_IN_ONE */

/*** Types */
struct ref_counted_obj {
unsigned int ref_count;
f_ref_cnt_free * dealloc;
};

/*** Functions */

void * NewRefCountedObj(
size_t size,
size_t alignment,
f_ref_cnt_free * dealloc
) {
void * result;
struct ref_counted_obj * ref;

/* Allocate the object */
result = pfxalloc(
sizeof *ref,
alignment,
size
);
if (!result)
return result;

ref = pfxget(result);
if (!ref) {
/* Uh oh; this is unexpected! */
return ref;
}

/* Populate accounting info */
ref->ref_count = 1;
ref->dealloc = dealloc;

return result;
}

void * GetRef(void * obj) {
struct ref_counted_obj * ref;

ref = pfxget(obj);
if (!ref)
return ref;

/* Increment ref count and check for wrap-around */
if (!++ref->ref_count) {
--ref->ref_count;
return NULL;
}
return obj;
}

void PutRef(void * obj) {
struct ref_counted_obj * ref;

ref = pfxget(obj);
if (!ref)
return;
/* Decrement ref count and check for zero */
if (!--ref->ref_count) {
ref->dealloc(obj);
free(ref);
}
return;
}


/**** Test program */
/* Shao Miller, 2011 */

#include <stdio.h>
#include <string.h>
#if !ALL_IN_ONE
#include <stdlib.h>
#include "pfxalloc.h"
#include "ref_cnt.h"
#endif /* !ALL_IN_ONE */

/*** Types */
struct foo {
double bobble;
size_t len;
char bear[1];
};

/*** Functions */
f_ref_cnt_free foo_dealloc;
void foo_dealloc(void * ptr) {
struct foo * bar;

bar = ptr;
printf(
"foo_dealloc(%p): {%f, %d, \"%s\"}\n",
ptr,
bar->bobble,
(int)bar->len,
bar->bear
);
return;
}

int main(void) {
struct foo * bar;
char laughsalot[] = "Laughs-a-lot";

bar = NewRefCountedObj(
sizeof *bar + sizeof laughsalot,
alignof (struct foo),
foo_dealloc
);
if (!bar) {
puts("NewRefCountedObj() failed!");
return EXIT_FAILURE;
}

bar->bobble = 3.14159;
bar->len = sizeof laughsalot;
memcpy(bar->bear, laughsalot, sizeof laughsalot);

/* Test getting a reference */
bar = GetRef(bar);
if (!bar) {
puts("GetRef() failed!");
PutRef(bar);
return EXIT_FAILURE;
}

/* We have two references */
PutRef(bar);
PutRef(bar);
return EXIT_SUCCESS;
}
 
T

Tim Rentsch

Keith Thompson said:
Tim Rentsch said:
Keith Thompson said:
For hashing, is the object representation of a 'void *' guaranteed to be
the same any time a 'void *' points to the same object? Is it likely?
(I think so.) :)

The standard only says pointers are represented in an
implementation-defined manner. But as long as the pointer value doesn't
change, I doubt the representation will. On most implementations, a
pointer is really just a 32- or 64-bit unsigned number, indicating a
certain spot in virtual memory.

Quibble: Pointer representation is unspecified, not
implementation-defined. [snip]

How do you reconcile this assertion with 6.2.6.1p2? Certainly
it looks like the representation of any pointer value stored in
an object would be (at least) implementation-defined.

Quite easily: by failing to read it carefully enough. :cool:}

But now that I have, I'm confused. Here's what it says:

1 The representations of all types are unspecified except as
stated in this subclause.

2 Except for bit-fields, objects are composed of contiguous
sequences of one or more bytes, the number, order, and
encoding of which are either explicitly specified or
implementation-defined.

3 Values stored in unsigned bit-fields and objects of type unsigned
char shall be represented using a pure binary notation.

As far as I can tell, *all* types have implementation-defined
representations; the section says so for non bit-fields and for
unsigned bit-fields, and I don't think there's enough wiggle room
to say that signed bit-fields have unspecified representations.

So why does paragraph 1 say "unspecified" rather than
"implementation-defined"? [snip]

Here's a possible idea: maybe paragraph 1 is talking about
representations generally (including, eg, registers), but
paragraph 2 is talking about representations specifically
only in objects. So values of a type can be represented
outside of being stored in an object, but what those
representations might be is all "under the hood", with no
documentation required. What do you think? Does that make
sense?
 
L

luser- -droog

Hi,

If I want to do simple reference counting for different structures I
would go like this:
[deletia]

Though if I have about ten structs, I have ten different functions to
increase and decrease the reference count. Is it, in some way, possible
to just provide one function for increasing and one function for
decreasing the reference count which can be used for all structs with a
reference count?


+1 for an excellent question leading to a very interesting thread!
 
K

Keith Thompson

Tim Rentsch said:
Keith Thompson said:
Tim Rentsch said:
For hashing, is the object representation of a 'void *' guaranteed to be
the same any time a 'void *' points to the same object? Is it likely?
(I think so.) :)

The standard only says pointers are represented in an
implementation-defined manner. But as long as the pointer value doesn't
change, I doubt the representation will. On most implementations, a
pointer is really just a 32- or 64-bit unsigned number, indicating a
certain spot in virtual memory.

Quibble: Pointer representation is unspecified, not
implementation-defined. [snip]

How do you reconcile this assertion with 6.2.6.1p2? Certainly
it looks like the representation of any pointer value stored in
an object would be (at least) implementation-defined.

Quite easily: by failing to read it carefully enough. :cool:}

But now that I have, I'm confused. Here's what it says:

1 The representations of all types are unspecified except as
stated in this subclause.

2 Except for bit-fields, objects are composed of contiguous
sequences of one or more bytes, the number, order, and
encoding of which are either explicitly specified or
implementation-defined.

3 Values stored in unsigned bit-fields and objects of type unsigned
char shall be represented using a pure binary notation.

As far as I can tell, *all* types have implementation-defined
representations; the section says so for non bit-fields and for
unsigned bit-fields, and I don't think there's enough wiggle room
to say that signed bit-fields have unspecified representations.

So why does paragraph 1 say "unspecified" rather than
"implementation-defined"? [snip]

Here's a possible idea: maybe paragraph 1 is talking about
representations generally (including, eg, registers), but
paragraph 2 is talking about representations specifically
only in objects. So values of a type can be represented
outside of being stored in an object, but what those
representations might be is all "under the hood", with no
documentation required. What do you think? Does that make
sense?

My impression is that the idea of representation applies only to
objects. The representation of a value not stored in an object is
irrelevant until and unless it's stored in an object.

I think your idea makes some sense, but I don't think it's what
6.2.6.1 is referring to.

I have no particular support for this.
 
C

Chris M. Thomasson

[...]
Given an adaptation of Mr. Chris M. Thomasson's macro:

#define ALIGNOF(type) (offsetof(struct{char c; type t;}, t))

(which, by the way, I just noticed doesn't appear to be guaranteed as
giving the same result every time due to a lack of tag, though that might
be highly unlikely)

We could have a 'malloc' wrapper:

void * malloc_aligned(size_t size, size_t alignment);

But then callers would need to:

some * p = malloc_aligned(
sizeof *p + optional_variable_length,
ALIGNOF(some)
);

where we'd forfeit the convenience of having 'some' appear just once.

You mean something like this crazy shi%:

http://codepad.org/Ot7HL6Te

? ;^o

[...]
 
J

James Kuyper

Keith Thompson wrote: ....

Do you think that the value of (-1 & 3)
corresponds to the way that an implementation
represents negative integers?

The way that the bit-wise operators behave is defined in terms of the
bits, even though C operators only operate on values, and bits are a
feature of the corresponding representation. This is less than ideal, IMO.

The only exception is the bit-wise shift operators; but interestingly
enough, the result is at best implementation-defined, and at worst,
undefined, whenever a shift would otherwise seem to involve a sign bit
that is non-zero.
 
I

Ian Collins

"io_x"<[email protected]> ha scritto nel messaggio

what about my try?

#include<stdio.h>
#include<stdlib.h>
#include<stdint.h>

// macro for types
#define u64 uint64_t
#define u32 uint32_t
#define u16 uint16_t
#define u8 uint8_t
#define uns unsigned
#define dbl double

// macro for function
#define P printf

Sane readers will have given up by here.
 
C

Chris M. Thomasson

Chris M. Thomasson said:
Chris M. Thomasson said:
We could have a 'malloc' wrapper:

void * malloc_aligned(size_t size, size_t alignment); [...]
where we'd forfeit the convenience of having 'some' appear just once.

You mean something like this crazy shi%:

http://codepad.org/Ot7HL6Te

I was allocating too much space in the code above. Here is an "improved"
version:

http://codepad.org/fOAFnYCZ

Sorry about that.

I think there is an error in `xmalloc_aligned()' because I subtract
`sizeof(void*)' from the aligned user object base address! This might not be
correct because `sizeof(void*)' could be `4' and `ALIGN_OF(void*)' (assume
100% correct result for a moment) just might be 128!


Therefore, I need to use something like:
___________________________________________
#define ALIGN_PTR_SZ \
(ALIGN_OF(void*) > sizeof(void*) \
? ALIGN_OF(void*) \
: sizeof(void*))
___________________________________________


as an offset wrt the aligned "user object" portion of the allocated
memory...

;^/
 
S

Shao Miller

Chris M. Thomasson said:
Chris M. Thomasson said:
news:[email protected]... [...]
We could have a 'malloc' wrapper:

void * malloc_aligned(size_t size, size_t alignment); [...]
where we'd forfeit the convenience of having 'some' appear just once.

You mean something like this crazy shi%:

http://codepad.org/Ot7HL6Te

I was allocating too much space in the code above. Here is an "improved"
version:

http://codepad.org/fOAFnYCZ

Sorry about that.

I think there is an error in `xmalloc_aligned()' because I subtract
`sizeof(void*)' from the aligned user object base address! This might not be
correct because `sizeof(void*)' could be `4' and `ALIGN_OF(void*)' (assume
100% correct result for a moment) just might be 128!

I think you mean "assume 100% incorrect result for a moment," right? If
it was a correct result, the alignment couldn't be greater than the
'sizeof'... The 'sizeof' must be an integer multiple of the alignment
requirement, else arrays wouldn't work. I might have misunderstood you. :)
Therefore, I need to use something like:
___________________________________________
#define ALIGN_PTR_SZ \
(ALIGN_OF(void*)> sizeof(void*) \
? ALIGN_OF(void*) \
: sizeof(void*))
___________________________________________


as an offset wrt the aligned "user object" portion of the allocated
memory...

;^/

Interesting code, by the way.
 
C

Chris M. Thomasson

Shao Miller said:
Chris M. Thomasson said:
[...]
We could have a 'malloc' wrapper:

void * malloc_aligned(size_t size, size_t alignment);
[...]
where we'd forfeit the convenience of having 'some' appear just once.

You mean something like this crazy shi%:

http://codepad.org/Ot7HL6Te

I was allocating too much space in the code above. Here is an "improved"
version:

http://codepad.org/fOAFnYCZ

Sorry about that.

I think there is an error in `xmalloc_aligned()' because I subtract
`sizeof(void*)' from the aligned user object base address! This might not
be
correct because `sizeof(void*)' could be `4' and `ALIGN_OF(void*)'
(assume
100% correct result for a moment) just might be 128!

I think you mean "assume 100% incorrect result for a moment," right? If
it was a correct result, the alignment couldn't be greater than the
'sizeof'... The 'sizeof' must be an integer multiple of the alignment
requirement, else arrays wouldn't work. I might have misunderstood you.
:)

Sorry; I was _really_ decoupling `ALIGN_OF()' with `sizeof()'!
 
C

Chris M. Thomasson

Chris M. Thomasson said:
Sorry; I was _really_ decoupling `ALIGN_OF()' with `sizeof()'!

think in terms of `sizeof(data-structure-A)' is less than alignment for said
`(data-structure-A)'. This can be fairly common.

sizeof(foo) can be 4
alignof(foo) might be 1024

Who knows right?
 
S

Shao Miller

Some comments on your code, for whatever the comments are worth...
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <assert.h>

#if ! defined (ALIGN_UINTPTR_TYPE)
# define ALIGN_UINTPTR_TYPE size_t
#endif

So it seems you allow for an override, above. Whatever the choice, the
result of casting a pointer to an integer type is
implementation-defined[6.3.2.3p6]. The result needn't be particularly
meaningful, though it'd probably be pretty sensible to convert to an
integer representing a linear address for an environment with such an
addressing model. An implementation needn't support 'intptr_t' nor
'uintptr_t', for example[7.18.1.4p1].
typedef char ALIGN_STATIC_ASSERT
[
sizeof(ALIGN_UINTPTR_TYPE) == sizeof(void*)
? 1 : -1
];

Ok so whatever the choice of this special type, its size must be equal
to 'sizeof (void *)'. I guess the idea is that a 'void *' is an
integer-in-disguise and casting to this integer type with the same size
ought not to "involve much"?
#define ALIGN_OF(mp_type) \
offsetof( \
struct \
{ \
char pad; \
mp_type type; \
}, \
type \
)

'mp' for "macro parameter"? :)
#define ALIGN_UP(mp_ptr, mp_align) \
((void*)( \
(((ALIGN_UINTPTR_TYPE)(mp_ptr)) + \
((mp_align) - 1)) & ~(((mp_align) - 1)) \
))

Above, you cast a pointer to an integer type (presumably). This assumes
that the result can be manipulated arithmetically, then cast back to a
pointer type[6.3.2.3p5], then the resulting pointer used. That's
probably true for several implementations, but a trap representation is
also a possible result.

You might "statically" assert the presence of 'uintptr_t' and use it,
since it has a few guarantees associated with it. It's still not
guaranteed (as far as I know) that manipulating the integer value
arithmetically has any relationship to alignment or what the
cast-to-pointer will point [in]to.

The bitwise operations are nice.
void*
xmalloc_aligned(
size_t header_size,
size_t object_size,
size_t align_size
){
size_t total_size;
unsigned char* tmp;
unsigned char* header;
unsigned char* header_ptr;
unsigned char* object;

align_size = align_size
? (size_t)ALIGN_UP(align_size, ALIGN_OF(void*))
: ALIGN_OF(void*);

Aha. Here you use 'ALIGN_UP' with an integer value as 'mp_ptr', so
'ALIGN_UP' is designed for both pointer and integers.

If I read correctly, the 'ALIGN_UP' will yield a 'void *', which you
cast to a 'size_t', above. But earlier on you allowed for a user to
specify a type other than the default 'size_t' as a preferred integer
type for pointer conversion. I see you have no choice here, since
'align_size' is a 'size_t'.

If only this computation didn't have to invoke pointer conversions,
since the macro arguments are integers and the result is an integer. Oh
well.

Each time you use 'ALIGN_OF', you have a "helper" struct without a tag.
Although it's probably pretty unlikely, it seems that an
implementation could choose different padding each time, despite the
same struct definition with the 'char' and the same 'mp_type' member.
total_size = (size_t)
ALIGN_UP(
header_size + sizeof(void*) +
object_size + ALIGN_OF(void*) + align_size,
ALIGN_OF(void*)
);

Hopefully the additions of these 'size_t' values don't wrap around. The
caller can always take the blame.
if (! (header = malloc(total_size))) return NULL;
tmp = ALIGN_UP(header + header_size, ALIGN_OF(void*));
object = ALIGN_UP(tmp + sizeof(void*), align_size);
header_ptr = object - sizeof(void*);
*((void**)header_ptr) = header;

return object;
}

Does the resulting layout look like this?:

[header_size] | padding | header_ptr | [object_size]
#define xmalloc_aligned_get(mp_object) \
((mp_object) \
? (*((void**)(((unsigned char*)mp_object) \
- sizeof(void*)))) \
: NULL)

So one takes a pointer, back-tracks to an immediately preceding 'void
*', then that pointer points to a header. Aha.
#define XMALLOC_ALIGNED(mp_header, mp_object) \
xmalloc_aligned( \
sizeof(mp_header), \
sizeof(mp_object), \
ALIGN_OF(mp_object) \
)

So the macro user needs to understand that this convenience macro takes
type-names.
void
xfree_aligned(
void* object
){
if (! object) return;
free(xmalloc_aligned_get(object));
}

typedef void (ref_count_func_dtor) (void*);

I'm not sure why function pointer 'typedef's are so popular when one can
'typedef' functions, as you have above. This 'typedef' can serve as a
"safety measure" in that if you declare:

ref_count_func_dtor some_func;

somewhere, the definition had better agree with the declared type. I'm
a bit confused by one of Microsoft's analysis tools which _appears_ (I
could be wrong) to require functions to be declared with function
pointer types prior to their definitions. *boggle*

I believe that parentheses are required in the 'typedef' for function
_pointer_ types, of course. (Heh.)
struct ref_count
{
unsigned count;
ref_count_func_dtor* fp_dtor;
};

void* ref_count_sys_create(
size_t object_size,
size_t align_size,
ref_count_func_dtor* fp_dtor
){
struct ref_count* self;
void* const object =
xmalloc_aligned(sizeof(*self), object_size, align_size);
if (! object) return NULL;
self = xmalloc_aligned_get(object);
self->count = 1;
self->fp_dtor = fp_dtor;
return object;
}

Nice and simple.
#define REF_COUNT_CREATE(mp_type, mp_fp_dtor) \
ref_count_sys_create( \
sizeof(mp_type), \
ALIGN_OF(mp_type), \
(mp_fp_dtor) \
)


#define ref_count_get(mp_object) \
xmalloc_aligned_get(mp_object)


void*
ref_count_acquire(
void* state
){
struct ref_count* const self = ref_count_get(state);
if (! self) return NULL;
++self->count;
return state;
}

Hopefully the reference count doesn't wrap around.
void
ref_count_release(
void* state
){
struct ref_count* const self = ref_count_get(state);
if (! self) return;
if (--self->count) return;
if (self->fp_dtor) self->fp_dtor(state);
free(self);
}

So simple, so nice.
struct foo
{
/*__declspec(align(128))*/ double state;
};


void
foo_ref_count_dtor(
void* state
){
struct foo* const self = state;

printf("foo_ref_count_dtor::%p - %f\n",
(void*)self, self->state);
}

Why not use 'state' instead of '(void*)self'? (Not that it matters.)
int main(void)
{
struct foo* foo1 =
REF_COUNT_CREATE(struct foo, foo_ref_count_dtor);

struct foo* foo2;

if (! foo1) return EXIT_FAILURE;

foo1->state = -665;

foo2 = ref_count_acquire(foo1);

foo2->state -= 1;

ref_count_release(foo1);
ref_count_release(foo2);

return EXIT_SUCCESS;
}

Pretty nifty. :)
 
S

Shao Miller

think in terms of `sizeof(data-structure-A)' is less than alignment for said
`(data-structure-A)'. This can be fairly common.

sizeof(foo) can be 4
alignof(foo) might be 1024

Who knows right?

Nope; you've lost me.

If we take your 'foo' example and do:

foo array[2];

The first element will presumably be properly aligned at some multiple
of 1024, then the next element will start 4 bytes later, which will be
improperly aligned at ((some multiple of 1024) plus 4).
 
S

Shao Miller

luser- -droog said:
An external table would avoid all the alignment-guessing for pre-C1X.
I'm too lazy to finish coding this, but what about a simple open-
addressed
hash-table?

Laziness overcome!

[...code...]

int main(void) {
ref_init();
char *s = strdup("apple");
char *t = strdup("banana");

print(s); /* apple */
print(t); /* banana */

ref_dec(s);
print(s); /* */
print(t); /* banana */

ref_dec(t);
print(s); /* */
print(t); /* */

return 0;
}

#endif
518(0)10:47 PM:~ 0> make rfct&& rfct
cc -g -Wall -c -o rfct.o rfct.c
cc rfct.o -o rfct
apple
banana
banana
519(0)10:47 PM:~ 0>

How interesting! You impose no special requirements on the programmers
to use overriding macros or wrapper functions for allocation functions,
you do not touch nor examine the state of the pointed-to objects, yet
your 'ref_dec' invocations behave differently! Pretty nifty.
 
S

Shao Miller

"io_x"<[email protected]> ha scritto nel messaggio

what about my try?

[...code...]

I find it a bit crammed and difficult to read, so here's an adaptation
of the code by performing preprocessor expansion, followed by an
'indent' invocation:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

union tipidiarray {
double a;
size_t v;
uint8_t *m;
};

uint32_t elemento = sizeof(union tipidiarray);

uint8_t *IniMem(uint32_t s)
{
uint8_t *p;
uint32_t *pu;
uint32_t c;

if (s == 0)
return 0;
c = s;
c += elemento;
if (c < s)
return 0;
p = malloc(c);
if (p == 0)
return 0;
pu = (uint32_t *) p;
p += elemento;
pu[0] = 1;
pu[1] = (uint32_t) p;
return p;
}

uint8_t *GetRef(uint8_t * v)
{
uint8_t *p;
uint32_t *pu;

if (v == 0)
return 0;
p = v - elemento;
pu = (uint32_t *) p;
if (pu[0] == 0 || pu[1] != (uint32_t) v)
return 0;
++pu[0];
if (pu[0] == 0) {
--pu[0];
return 0;
}
return v;
}

uint32_t GetRefCounter(uint8_t * v)
{
uint8_t *p;
uint32_t *pu;

if (v == 0)
return 0;
p = v - elemento;
pu = (uint32_t *) p;
if (pu[1] != (uint32_t) v)
return (uint32_t) - 1;
return pu[0];
}

uint8_t *PutRef(uint32_t * ris, uint8_t * v)
{
uint8_t *p;
uint32_t *pu;

if (ris == 0 || v == 0)
return 0;
*ris = 0;
p = v - elemento;
pu = (uint32_t *) p;
if (pu[0] == 0 || pu[1] != (uint32_t) v) {
*ris = -1;
return 0;
}
--pu[0];
if (pu[0] == 0) {
pu[1] = 0;
free(p);
}
return 0;
}

int main(void)
{
double *arrd, *pad;
uint32_t *arru, *elm, ris;

if (2 * sizeof(uint32_t) > elemento) {
printf("Errore di inizializzazione\n");
return 0;
}
arrd = (double *)IniMem(10 * sizeof(double));
arrd[0] = 1.111;
pad = (double *)GetRef((uint8_t *) arrd);
if (pad == 0) {
printf("Errore della funzione GetRef()\n");
printf("o memoria insufficiente\n");
exit(0);
}
printf("val=%f nref=%u\n", pad[0],
(unsigned)GetRefCounter((uint8_t *) arrd));
pad = (double *)PutRef(&ris, (uint8_t *) pad);
printf("val=%f nref=%u pad=%p ris=%u\n",
arrd[0], (unsigned)GetRefCounter((uint8_t *) arrd),
(uint8_t *) pad, (unsigned)ris);

arrd = (double *)PutRef(&ris, (uint8_t *) pad);
printf("nref=%u ris=%u\n", (unsigned)GetRefCounter((uint8_t *)
arrd),
(unsigned)ris);
return 0;
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Members online

No members online now.

Forum statistics

Threads
474,091
Messages
2,570,604
Members
47,224
Latest member
Gwen068088

Latest Threads

Top