Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Friday afternoon C trivia:

3 views
Skip to first unread message

Nils

unread,
Nov 27, 2009, 8:37:44 AM11/27/09
to
Since there are so many experienced C programmers on this group. How
about a little test? Just for fun...

I have four pointer definitions:

typdef void * LPVOID;

1. const void * ptr = ...
2. const LPVOID ptr = ...
3. void * const ptr = ...
4. LPVOID const ptr = ...

Questions:

1. Which pointers point to constant memory. E.g. you can't change the
memory behind the pointer?

2. Which pointers are constant. E.g. you can't assign them a different
value?


Have fun! I already had my share of embarrassed workmate faces :-)

Cheers,
Nils Pipenbrinck

David Brown

unread,
Nov 27, 2009, 8:58:52 AM11/27/09
to

It might be a bit of fun doing such tests, but anyone who actually
writes that sort of nonsense in real code should be embarrassed. If it
is not immediately obvious how the consts and pointers fit together, the
types should be broken down to typedefs until it /is/ obvious.

Mel

unread,
Nov 27, 2009, 9:42:49 AM11/27/09
to
David Brown wrote:

> It might be a bit of fun doing such tests, but anyone who actually
> writes that sort of nonsense in real code should be embarrassed. If it
> is not immediately obvious how the consts and pointers fit together, the
> types should be broken down to typedefs until it /is/ obvious.

It's no big deal. With `const ptr`, ptr cannot be changed. With `const
*ptr`, *ptr cannot be changed. End of story.

Mel.


mac

unread,
Nov 27, 2009, 10:01:15 AM11/27/09
to
> > typdef void * LPVOID;

> It might be a bit of fun doing such tests, but anyone who actually
> writes that sort of nonsense in real code should be embarrassed. If it
> is not immediately obvious how the consts and pointers fit together, the
> types should be broken down to typedefs until it /is/ obvious.

Like anyone who thinks LPVOID is clearer than void *

David Brown

unread,
Nov 27, 2009, 10:26:53 AM11/27/09
to

Correct. But it is still possible to write things in confusing ways,
such as by putting "const" in different places, changing where you put
spaces around the "*", or for the real obfustication experts, mixing
pointer and non-pointer declarations in the same statement.

My point is just that code should be clear. If people reading the code
find it hard to figure out where the "const" applies, the code is badly
written.

Note that this depends highly on your expected audience. In some
development groups the meaning of something like "volatile uint8_t *
const p" is obvious, in others it is not. In the OP's case, his
colleagues had trouble with some of these constructs - therefore using
them in his work would be bad programming.

Vladimir Vassilevsky

unread,
Nov 27, 2009, 10:33:42 AM11/27/09
to

mac wrote:

1. Using pointers to void is malpractice.

2. LPVOID is long pointer, which is not the same thing as void *.


Vladimir Vassilevsky
DSP and Mixed Signal Design Consultant
http://www.abvolt.com

David Brown

unread,
Nov 27, 2009, 10:33:13 AM11/27/09
to

That depends on the circumstances, but if you mean that gratuitous
typedefs make the code less clear, then I agree.

Nils

unread,
Nov 27, 2009, 11:40:08 AM11/27/09
to
David Brown wrote:

> In the OP's case, his colleagues had trouble with
> some of these constructs - therefore using
> them in his work would be bad programming.

Nah - my colleagues know where to put the const-keyword.. Most of the
time at least. And keep in mind that this is not a real world example.

It just happend that I stumbled upon the little difference that the
typedef makes, and I'm still fascinated that even the experienced among
us didn't got it right...

Most of us have 20 years of experience and didn't knew this.


typedef void * LPVOID;

void testcase (void)
{
char something[10];

// two pointers.. Seem to do the same thing:
const void * ptr1 = 0;
const LPVOID ptr2 = 0;

// but they behave different:
ptr1 = something; // compiles fine
ptr2 = something; // gives compiler error..
}


Tim Wescott

unread,
Nov 27, 2009, 12:14:38 PM11/27/09
to
On Fri, 27 Nov 2009 14:58:52 +0100, David Brown wrote:

> Nils wrote:
>> Since there are so many experienced C programmers on this group. How
>> about a little test? Just for fun...
>>
>> I have four pointer definitions:
>>
>> typdef void * LPVOID;
>>
>> 1. const void * ptr = ...
>> 2. const LPVOID ptr = ...
>> 3. void * const ptr = ...
>> 4. LPVOID const ptr = ...
>>
>> Questions:
>>
>> 1. Which pointers point to constant memory. E.g. you can't change the
>> memory behind the pointer?
>>
>> 2. Which pointers are constant. E.g. you can't assign them a different
>> value?
>>
>>
>> Have fun! I already had my share of embarrassed workmate faces :-)
>>
>>
> It might be a bit of fun doing such tests, but anyone who actually
> writes that sort of nonsense in real code should be embarrassed.

Possibly. But some of us have to read code that others have written.

> If it
> is not immediately obvious how the consts and pointers fit together, the
> types should be broken down to typedefs until it /is/ obvious.

Which can add to the confusion if you spread the typedefs over several
header files.

--
www.wescottdesign.com

D Yuniskis

unread,
Nov 27, 2009, 2:40:48 PM11/27/09
to
David Brown wrote:
> Mel wrote:
>> David Brown wrote:
>>
>>> It might be a bit of fun doing such tests, but anyone who actually
>>> writes that sort of nonsense in real code should be embarrassed. If it
>>> is not immediately obvious how the consts and pointers fit together, the
>>> types should be broken down to typedefs until it /is/ obvious.
>>
>> It's no big deal. With `const ptr`, ptr cannot be changed. With
>> `const *ptr`, *ptr cannot be changed. End of story.
>
> Correct. But it is still possible to write things in confusing ways,
> such as by putting "const" in different places, changing where you put
> spaces around the "*", or for the real obfustication experts, mixing
> pointer and non-pointer declarations in the same statement.
>
> My point is just that code should be clear. If people reading the code
> find it hard to figure out where the "const" applies, the code is badly
> written.

Agreed. It is far too easy for people reading code ex post facto
to "read what they want to read" (i.e., "see what they hope to see")
and not what the code actually *says*. Writing good code means
writing code that is easily understood and so needless careless
mistakes are minimized.

For example, I parenthesize arithmetic expressions involving anything
other than +- and */ as it is too easy for people to misremember
rules of precedence (and, in some languages, the rules are
quite different than what you would expect! e.g., how is
"x = 5 < 2" evaluated?)

> Note that this depends highly on your expected audience. In some
> development groups the meaning of something like "volatile uint8_t *
> const p" is obvious, in others it is not. In the OP's case, his
> colleagues had trouble with some of these constructs - therefore using
> them in his work would be bad programming.

I *dis*agree, here. How you write should be consistent as
you can't *know* your ultimate audience. Code that you
wrote years ago that was "bleeding edge" may end up being
maintained by "new hires" today.

mac

unread,
Nov 27, 2009, 5:06:56 PM11/27/09
to

> >>>typdef void * LPVOID;

> > Like anyone who thinks LPVOID is clearer than void *
>
> 1. Using pointers to void is malpractice.
>
> 2. LPVOID is long pointer, which is not the same thing as void *.

See above. It might claim to be "long", but you have to look up the
declaration to see that it isn't.

Reminds me of a Pascal textbook that included exapmples with
const NINE = 9;
Presumably this allows the program maintainer to easily change the value
of NINE to something else, such as 7.

Since this is comp.arch.embedded, it's excusable that we're still
dealing in "long" and "short" pointers. Is that the same as far and near?

Clifford Heath

unread,
Nov 27, 2009, 5:51:35 PM11/27/09
to
Nils wrote:
> Since there are so many experienced C programmers on this group. How
> about a little test? Just for fun...
>
> I have four pointer definitions:
>
> typdef void * LPVOID;
>
> 1. const void * ptr = ...
> 2. const LPVOID ptr = ...
> 3. void * const ptr = ...
> 4. LPVOID const ptr = ...
>
> Questions:
>
> 1. Which pointers point to constant memory. E.g. you can't change the
> memory behind the pointer?

The correct answer is, none of them. You can't assign a void type.
You need to cast the pointer to something else, and then it's not
the same pointer, is it?

Had you done the same thing with char however, the answer is based
on a simple principle; const applies to the thing to its immediate
left, unless it's at the start, when it applies to the leftmost thing.

So case 1 disallows changing the underlying memory, 2,3,4 allow it.

> 2. Which pointers are constant. E.g. you can't assign them a different
> value?

Cases 2,3,4.


Clifford Heath.

42Bastian Schick

unread,
Nov 28, 2009, 2:21:07 PM11/28/09
to
On Fri, 27 Nov 2009 17:40:08 +0100, Nils <n.pipe...@cubic.org>
wrote:

>typedef void * LPVOID;
>
>void testcase (void)
>{
> char something[10];
>
> // two pointers.. Seem to do the same thing:
> const void * ptr1 = 0;
> const LPVOID ptr2 = 0;
>
> // but they behave different:
> ptr1 = something; // compiles fine
> ptr2 = something; // gives compiler error..
>}

Hmm, it should be, as you declare ptr2 to be constant.

Note:
const void * ptr1 is equivalent to void const * ptr1
telling the compiler that what ptr1 points to is constant, not ptr1
itself.
Otherwise const <anytype> x tells the compiler that x of <anytype> is
constant, so no matter what <anytype> is.

const void * const ptr1;
is the equivalent to
const LPVOID ptr1;

BTW: In (embedded) compilers one needs often this:

const char * const table[] = {
"Hello","World"
};
to be sure both the strings as well as the table are in ROM.

writing const char * table[] ...
will place the table into .data section (=> RAM).

--
42Bastian
Do not email to bast...@yahoo.com, it's a spam-only account :-)
Use <same-name>@monlynx.de instead !

Thad Smith

unread,
Nov 28, 2009, 6:20:41 PM11/28/09
to
Vladimir Vassilevsky wrote:

> 1. Using pointers to void is malpractice.

Pointers to void are a generic pointer, used to point to data of any
type for moving, sending as message payload, etc. The standard C
library uses them to good effect and so do I.

--
Thad

Vladimir Vassilevsky

unread,
Nov 29, 2009, 11:02:51 AM11/29/09
to

Thad Smith wrote:
> Vladimir Vassilevsky wrote:
>
>> 1. Using pointers to void is malpractice.
>
>
> Pointers to void are a generic pointer, used to point to data of any
> type for moving, sending as message payload, etc.

So the information about type is lost. No good.
Seen many problems becase somebody liked the style of programming with
void pointers.

> The standard C
> library uses them to good effect and so do I.

C is outdated dialect of C++, which sometimes has to be used for the
resource constrained systems.

Thad Smith

unread,
Nov 29, 2009, 7:07:39 PM11/29/09
to
Vladimir Vassilevsky wrote:
> Thad Smith wrote:
>> Vladimir Vassilevsky wrote:
>>> 1. Using pointers to void is malpractice.
>>
>> Pointers to void are a generic pointer, used to point to data of any
>> type for moving, sending as message payload, etc.
>
> So the information about type is lost. No good.
> Seen many problems becase somebody liked the style of programming with
> void pointers.

Certainly if you are passing a reference to a structure and the called
function only deals with that structure, the argument type should be a
pointer to the specific structure.

If you have a function to send a message of arbitrary content, it makes
sense to me to pass a generic pointer and size. You could instead write
a wrapper function for each type of entity to be sent or even a
standalone separate function for sending each data type, but I don't see
it as sufficient value, as long as it is reasonably easy to use a
generic function correctly. I use malloc(), memcpy(), memset(), and
memcpy(), for example, without problems.

What experiences have you had to influence your design? Can you give an
example of problems seen or better design through typed pointers where
others would have used generic pointers?

> C is outdated dialect of C++, which sometimes has to be used for the
> resource constrained systems.

Well, most of my C programming is for resource constrained systems.

--
Thad

David Brown

unread,
Nov 30, 2009, 5:33:52 AM11/30/09
to
D Yuniskis wrote:

> David Brown wrote:
>> Note that this depends highly on your expected audience. In some
>> development groups the meaning of something like "volatile uint8_t *
>> const p" is obvious, in others it is not. In the OP's case, his
>> colleagues had trouble with some of these constructs - therefore using
>> them in his work would be bad programming.
>
> I *dis*agree, here. How you write should be consistent as
> you can't *know* your ultimate audience. Code that you
> wrote years ago that was "bleeding edge" may end up being
> maintained by "new hires" today.

You /do/ know something about your audience - they will be people at
least roughly qualified to work on the code in question. C code written
for the Win32 api would be gobbledegook to a Linux kernel programmer,
and vice versa, because they are written in different styles and with
different standard types, macros, common functions, naming conventions, etc.

But you are right that you shouldn't make extra and unnecessary
assumptions about the reader's knowledge and experience.

Vladimir Vassilevsky

unread,
Nov 30, 2009, 10:20:16 AM11/30/09
to

Thad Smith wrote:

> Vladimir Vassilevsky wrote:
>
>> Thad Smith wrote:
>>
>>> Vladimir Vassilevsky wrote:
>>>
>>>> 1. Using pointers to void is malpractice.
>>>
>>> Pointers to void are a generic pointer, used to point to data of any
>>> type for moving, sending as message payload, etc.
>>
>> So the information about type is lost. No good.
>> Seen many problems becase somebody liked the style of programming with
>> void pointers.
>
>
> Certainly if you are passing a reference to a structure and the called
> function only deals with that structure, the argument type should be a
> pointer to the specific structure.
>
> If you have a function to send a message of arbitrary content, it makes
> sense to me to pass a generic pointer and size.

<Templates> are safer and more efficient.

> What experiences have you had to influence your design? Can you give an
> example of problems seen or better design through typed pointers where
> others would have used generic pointers?

1. There was a large project which included PC part, embedded DSP part
and a library of common functions shared between PC and DSP. Different
toolsets, different build chains. Several programmers worked on the
project; one of them decided to save effort by using void pointers for
passing data. Cleaning the bugs which appeared because of that was a
nasty job.

2. A function like memcpy() can be more efficient if it knows what is
the data, how it is aligned, if the size is a multiple of dwords, where
is useful data and where is padding, etc.

3. "Shallow" copy problem. When manipulating objects, I generally don't
have to know what is inside and if they are referencing to something.
Therefore I avoid using "generic" functions and use methods specific to
those objects instead.


>> C is outdated dialect of C++, which sometimes has to be used for the
>> resource constrained systems.
>
> Well, most of my C programming is for resource constrained systems.

The development time and the engineering costs are limited also.

D Yuniskis

unread,
Nov 30, 2009, 2:32:03 PM11/30/09
to
David Brown wrote:
> D Yuniskis wrote:
>> David Brown wrote:
>>> Note that this depends highly on your expected audience. In some
>>> development groups the meaning of something like "volatile uint8_t *
>>> const p" is obvious, in others it is not. In the OP's case, his
>>> colleagues had trouble with some of these constructs - therefore
>>> using them in his work would be bad programming.
>>
>> I *dis*agree, here. How you write should be consistent as
>> you can't *know* your ultimate audience. Code that you
>> wrote years ago that was "bleeding edge" may end up being
>> maintained by "new hires" today.
>
> You /do/ know something about your audience - they will be people at
> least roughly qualified to work on the code in question. C code written
> for the Win32 api would be gobbledegook to a Linux kernel programmer,
> and vice versa, because they are written in different styles and with
> different standard types, macros, common functions, naming conventions,
> etc.

That's only true if you work in a particular niche.
I frequently move code from desktop platforms to embedded
(or vice versa). The types of people who code in each
are vastly different (IME). It's always frustrating,
for example, watching someone come fresh from school or
The PC World and think they can just start writing
code for an embedded system on Day One -- the disbelief
they have over how things just don't work the same, etc.

Or, vice versa.

E.g., somoene coming from the embedded world sees no
difference in:

for (i = 0; i < MAX_I; i++)
for (j = 0; j < MAX_J; j++)
x[i][j] = foo...;

and:

for (j = 0; j < MAX_J; j++)
for (i = 0; i < MAX_I; i++)
x[i][j] = foo...;

but someone used to working in a desktop world *knows*
(or *should* know) the difference!

Coming from a hardware & assembly language background,
I instinctively use pointers for *everything*. When
newcomers look at my code and see me invoking functions
*through* pointers and having those functions *return*
pointers to other functions, etc. can get upset as
it's not something they are used to doing. Dispatching
through tables of function pointers, etc.

Commentary can go a long way to helping -- assuming
you don't *waste* comments (which tends to result in
"comment rot" as those useless comments tend not to
get maintained).

I've also found that people often *think* they know
things that they actually *don't* and this misunderstanding
can be *reinforced* or *corrected* depending a lot on
"style". E.g., chosing a do-while() instead of a while()
loop to reinforce the fact that you *must* pass through
the loop at least once.

> But you are right that you shouldn't make extra and unnecessary
> assumptions about the reader's knowledge and experience.

Well, you *can* -- but, be prepared for them to break your
code! :>

I find that consistency helps people (including myself
when I have to revisit some "old code") focus on what
is actually happening instead of getting caught up in
syntax-related issues. Here, cut and paste can be a
*real* win if you deliberately exploit the similarity in
code, naming and commentary: "Oh, I already saw something
*just* like this a page earlier so I know what it is
doing..." (of course, you also have to make any changes
very deliberate to alert the reader to the fact that this
*isn't* the same as what he recently encountered)

Stefan Reuther

unread,
Nov 30, 2009, 1:56:44 PM11/30/09
to
Vladimir Vassilevsky wrote:
> Thad Smith wrote:
>> Certainly if you are passing a reference to a structure and the called
>> function only deals with that structure, the argument type should be a
>> pointer to the specific structure.
>>
>> If you have a function to send a message of arbitrary content, it
>> makes sense to me to pass a generic pointer and size.
>
> <Templates> are safer and more efficient.

We were talking about resource-constrained systems, weren't we?
Templates are not Generics, thus templatizing code blows up your binary
size, and complicates linking a lot. If you implement something like
Windows' FindWindow as a function template in a header file, can you
tell me where the object code will end up?

The C++ solution would be to send a callback object with a virtual
function, of course. On architectures that support it.

>> What experiences have you had to influence your design? Can you give
>> an example of problems seen or better design through typed pointers
>> where others would have used generic pointers?
>
> 1. There was a large project which included PC part, embedded DSP part
> and a library of common functions shared between PC and DSP. Different
> toolsets, different build chains. Several programmers worked on the
> project; one of them decided to save effort by using void pointers for
> passing data. Cleaning the bugs which appeared because of that was a
> nasty job.

This is one error source amongst many. Keeping code size low in heavily
templatized code is an even nastier job. Chasing void pointers may be
your anecdote. Being the one who developed our bootloader, chasing bloat
is mine.

> 2. A function like memcpy() can be more efficient if it knows what is
> the data, how it is aligned, if the size is a multiple of dwords, where
> is useful data and where is padding, etc.

Apples vs. Oranges. Of course we don't use memcpy if a single assignment
also does. On the other hand, only few compilers optimize a
for (i = 0; i < N; ++i)
pDestByte[i] = pSrcByte[i];
into "check if it's aligned, if it isn't, make it aligned, then use
word-wise copy". 'memcpy' implementations often do.

But even then, whereas it is at least possible to implement a typed
'memcpy', I definitely don't want to implement typed 'read' and 'write'
in my filesystem just to get rid of void pointers.

>>> C is outdated dialect of C++, which sometimes has to be used for the
>>> resource constrained systems.
>>
>> Well, most of my C programming is for resource constrained systems.
>
> The development time and the engineering costs are limited also.

Unless we're talking about huge multi-megabyte systems with gigantic
user interfaces, I wouldn't say C++ saves so much engineering costs.
After all, you can not just take the next student from around the corner
who uses all the neat tricks he read in chapter 1 of the Stroustrup
book. You need people who understand what the compiler will make out of
their giant abstract virtual template monster (newbie question #1: why
cannot I use a member function as interrupt handler?).

Plain C just makes it harder to produce monsters of *that* particular
type. Sure, it still allows monsters of different type ("huge while()
loop and fifty gazillion boolean flags"), but C++ isn't immune to that
either.


Stefan

ArarghMai...@not.at.arargh.com

unread,
Nov 30, 2009, 3:59:06 PM11/30/09
to
On Mon, 30 Nov 2009 12:32:03 -0700, D Yuniskis
<not.goi...@seen.com> wrote:

<snip>


>E.g., somoene coming from the embedded world sees no
>difference in:
>
>for (i = 0; i < MAX_I; i++)
> for (j = 0; j < MAX_J; j++)
> x[i][j] = foo...;
>
>and:
>
>for (j = 0; j < MAX_J; j++)
> for (i = 0; i < MAX_I; i++)
> x[i][j] = foo...;
>
>but someone used to working in a desktop world *knows*
>(or *should* know) the difference!

Ok, I give up. Besides one accessing the array sequentially, and the
other hopping around, what is the difference?


BTW, I am a 'desktop' type. :-)

And in years past, a minicomputer programmer. But I bet they are
mostly gone, now. :-)

<snip>
--
ArarghMail911 at [drop the 'http://www.' from ->] http://www.arargh.com
BCET Basic Compiler Page: http://www.arargh.com/basic/index.html

To reply by email, remove the extra stuff from the reply address.

Arlet

unread,
Nov 30, 2009, 7:03:12 PM11/30/09
to
On Mon, 30 Nov 2009 14:59:06 -0600, ArarghMail911NOSPAM wrote:

>>E.g., somoene coming from the embedded world sees no
>>difference in:
>>
>>for (i = 0; i < MAX_I; i++)
>> for (j = 0; j < MAX_J; j++)
>> x[i][j] = foo...;
>>
>>and:
>>
>>for (j = 0; j < MAX_J; j++)
>> for (i = 0; i < MAX_I; i++)
>> x[i][j] = foo...;
>>
>>but someone used to working in a desktop world *knows*
>>(or *should* know) the difference!
>
> Ok, I give up. Besides one accessing the array sequentially, and the
> other hopping around, what is the difference?

The 2nd one, which is hopping around, may have really bad cache behavior

ArarghMai...@not.at.arargh.com

unread,
Nov 30, 2009, 7:54:18 PM11/30/09
to
On Tue, 01 Dec 2009 01:03:12 +0100, Arlet <usen...@ladybug.xs4all.nl>
wrote:

That was pretty obvious, and mostly true for any random access.

Does mean to imply that embedded processors don't have any cache? :-)

D Yuniskis

unread,
Nov 30, 2009, 8:06:09 PM11/30/09
to

Yes, though that's not the real issue.

Most desktop environments (with non-toy OS's) use paged
memory under a VM. As you hop around, you reference
one page, then another, then the first page again, etc.
(assuming MAX_I/MAX_J are big and/or the object (X)
straddles a page boundary). As such, the system can thrash
in low memory conditions -- swap out the page containing
one x[][] and fault in the page containing the next
referenced x[][]; then swap *that* page out and swap back
in the previous page, etc.

The performance hit can be *huge*.

This behavior isn't common in most embedded systems because
all memory is physical memory (note that this is becoming
less true as embedded systems creep into bigger applications).

So, ALL ELSE BEING EQUAL (i.e., assume foo... has no side effects),
why write code that could end up exhibiting this sort of behavior?

Pertti Kellomaki

unread,
Dec 1, 2009, 3:35:47 AM12/1/09
to
Stefan Reuther wrote:
> We were talking about resource-constrained systems, weren't we?
> Templates are not Generics, thus templatizing code blows up your binary
> size, and complicates linking a lot.

Hopefully the development system is not resource constrained,
just the target. I know that a half-baked implementation can
do stupid things like produce two instances of a template even
though the resulting code is identical, but this is a quality
of implementation issue, not something intrinsic to templates.
Templating is a purely compile time phenomenon, so there is no
reason why it should affect binary size at all.

In fact, I would argue that compilers should be able to exploit
templating to reduce binary size. It is not unheard of to have
functions with identical algorithmic behavior but different
signatures. There may be compilers out there that aggressively
merge such functions if they result in the same machine code,
but clearly it would be more efficient to examine known instances
of a template rather than to examine all pairs of functions.
--
Pertti

John Devereux

unread,
Dec 1, 2009, 3:43:41 AM12/1/09
to
D Yuniskis <not.goi...@seen.com> writes:

However even gcc is able to optimize this away AIUI (gcc 4.4+, see
-f-loop-*, e.g. -floop-interchange).

--

John Devereux

D Yuniskis

unread,
Dec 1, 2009, 4:20:43 AM12/1/09
to
John Devereux wrote:
>>>> Ok, I give up. Besides one accessing the array sequentially, and the
>>>> other hopping around, what is the difference?

>>> The 2nd one, which is hopping around, may have really bad cache behavior

>> Yes, though that's not the real issue.
>>
>> Most desktop environments (with non-toy OS's) use paged
>> memory under a VM. As you hop around, you reference
>> one page, then another, then the first page again, etc.
>> (assuming MAX_I/MAX_J are big and/or the object (X)
>> straddles a page boundary). As such, the system can thrash
>> in low memory conditions -- swap out the page containing
>> one x[][] and fault in the page containing the next
>> referenced x[][]; then swap *that* page out and swap back
>> in the previous page, etc.
>>
>> The performance hit can be *huge*.
>>
>> This behavior isn't common in most embedded systems because
>> all memory is physical memory (note that this is becoming
>> less true as embedded systems creep into bigger applications).
>>
>> So, ALL ELSE BEING EQUAL (i.e., assume foo... has no side effects),
>> why write code that could end up exhibiting this sort of behavior?
>
> However even gcc is able to optimize this away AIUI (gcc 4.4+, see
> -f-loop-*, e.g. -floop-interchange).

You'll note that there are many embedded system targets that
GCC doesn't support :> And, you have to understand the nature
of the problem in order to know/suspect that you would want/need
to deal with it (if so, why not just code it "right" in the
first place?)

But, the point being made was consistency in coding (style,
practice, etc.). If "those who follow" can see this
consistency throughout your "product" (your product is,
after all, the code that you write!), then they can more
easily pick up on what you are trying to do instead of
getting distracted by syntax, etc. And, conversely, when they
see you doing something *different*, they are alerted to the
fact that something is "special", here. E.g., "Hmmm... why did
he reverse his normal order of processing subscripts?"

Bill Davy

unread,
Dec 1, 2009, 4:32:00 AM12/1/09
to
"D Yuniskis" <not.goi...@seen.com> wrote in message
news:hf2mma$vab$1...@aioe.org...


Surely the first version allows the pointer x[i] to be calculated once,
perhaps held in a register (even DPTR on an 8051) and the inner loop then
simply increments that pointer. A good compiler would also then move j to a
register (if it fits) and decrement it to zero - an idiom much used by old C
hands.

Embedded with cache? That's' really luxurious!


Paul Carpenter

unread,
Dec 1, 2009, 7:02:49 AM12/1/09
to
In article <hf1pn1$oe$1...@aioe.org>, not.goi...@seen.com says...

In some embedded systems you may need to do this for operations
on a captured image or part of it, for processing, so even in
physical memory it can have an accumulative effect. If the object 'x'
is small (or standard type char/short/int..) the processor is most
likely to have simple INC instructions which require less fetches
than an ADD larger number.

This may not seem a lot, but if you are scanning a block of image
you can easily end up doing this thousands, if not hundreds of
thousands of times.

Yes, it is easier to do some of these things in hardware, but if
the function is only used a few times it is sometimes easier and
cheaper to do this in software.

Many software compression of images routines require doing similar
methods as the first, and the outer loop(s) may have large
offsets between inner loop runs.

> So, ALL ELSE BEING EQUAL (i.e., assume foo... has no side effects),
> why write code that could end up exhibiting this sort of behavior?
>

--
Paul Carpenter | pa...@pcserviceselectronics.co.uk
<http://www.pcserviceselectronics.co.uk/> PC Services
<http://www.pcserviceselectronics.co.uk/fonts/> Timing Diagram Font
<http://www.gnuh8.org.uk/> GNU H8 - compiler & Renesas H8/H8S/H8 Tiny
<http://www.badweb.org.uk/> For those web sites you hate

John Devereux

unread,
Dec 1, 2009, 9:00:13 AM12/1/09
to
D Yuniskis <not.goi...@seen.com> writes:

I don't really disagree. But note that the ones GCC does not support -
i.e. some crufty old 8 & 16 bit parts - are precisely those that would
see no benefit anyway :)

Right now there is still a good argument for this type of manual
optimisation, but this seems likely to disappear shortly IMO.

> But, the point being made was consistency in coding (style,
> practice, etc.). If "those who follow" can see this
> consistency throughout your "product" (your product is,
> after all, the code that you write!), then they can more
> easily pick up on what you are trying to do instead of
> getting distracted by syntax, etc. And, conversely, when they
> see you doing something *different*, they are alerted to the
> fact that something is "special", here. E.g., "Hmmm... why did
> he reverse his normal order of processing subscripts?"

--

John Devereux

D Yuniskis

unread,
Dec 1, 2009, 12:32:18 PM12/1/09
to
John Devereux wrote:
> D Yuniskis <not.goi...@seen.com> writes:
>
>> John Devereux wrote:
>> You'll note that there are many embedded system targets that
>> GCC doesn't support :> And, you have to understand the nature
>> of the problem in order to know/suspect that you would want/need
>> to deal with it (if so, why not just code it "right" in the
>> first place?)
>
> I don't really disagree. But note that the ones GCC does not support -
> i.e. some crufty old 8 & 16 bit parts - are precisely those that would
> see no benefit anyway :)
>
> Right now there is still a good argument for this type of manual
> optimisation, but this seems likely to disappear shortly IMO.

I'm not looking at it as an optimization. That implies
an after-the-fact activity. Rather, this is just a
*coding style*. Something DECIDED UPON ahead of time
(for one reason or another -- I've just illustrated a
particular reason that influences that choice) and then
applied consistently UNLESS IT SHOULDN'T BE.

E.g., do you use:

for (i = foo; i < bar; i++)

or:

for (i = foo; i < bar; ++i)

as both are *functionally* equivalent. I suspect you
CONSISTENTLY use one form, right? :>

Stefan Reuther

unread,
Dec 1, 2009, 1:10:11 PM12/1/09
to
Pertti Kellomaki wrote:
> Stefan Reuther wrote:
>> We were talking about resource-constrained systems, weren't we?
>> Templates are not Generics, thus templatizing code blows up your binary
>> size, and complicates linking a lot.
>
> Hopefully the development system is not resource constrained,
> just the target. I know that a half-baked implementation can
> do stupid things like produce two instances of a template even
> though the resulting code is identical, but this is a quality
> of implementation issue, not something intrinsic to templates.

So far, all schemes of template management I've encountered had their
drawbacks in practice.

The "Borland method" - placing all template instances in all object
files, and sorting out duplicates at link time - so far has been the
friendliest one, although it blows up object file sizes and link times,
and in practice linkers don't manage to eliminate all dupes. Some
methods appear a few hundred times in our final binary.

The "repository" method - putting template instances into separate
object files at magic places -, and the "association" method - picking
an object file using the template at random and placing the template
instance there - have bad interaction with static libraries. We're using
the same set of object files to build multiple binaries. When building
a.exe, the compiler decides std::string goes into obj1.o, when building
b.exe, it decides std::string goes into obj2.o - boom.

All these methods have the disadvantage that you'll never know where
your code will end up. We're in comp.arch.embedded? Unlike on desktop
environments, where all memory is equal, I occasionally need to assign
specific code into specific sections, using a linker file. When I want
to put a particular template instance into L1 memory, I don't know which
object file to use. And when I want a specific object file, I don't know
whether an unexpected template instance will fill up my precious L1.

> Templating is a purely compile time phenomenon, so there is no
> reason why it should affect binary size at all.

Templates are instantiated to generate code. Unless you're using
templates that are completely inlined and optimized away (which is
indeed useful, and which I use all the time), of course it affects the
binary size.

> In fact, I would argue that compilers should be able to exploit
> templating to reduce binary size. It is not unheard of to have
> functions with identical algorithmic behavior but different
> signatures. There may be compilers out there that aggressively
> merge such functions if they result in the same machine code,
> but clearly it would be more efficient to examine known instances
> of a template rather than to examine all pairs of functions.

Do these compilers/linkers actually exist? I doubt it, and just hoping
that they might exist someday doesn't help me today.

Such a feature would be pretty hard to do standards-conformant anyway.
In particular, the compiler may *not* merge functions of identical
signature, because programmers can expect this to work
void (*foo)() = func1;
assert(foo != func2);
even if func1 and func2 have the same body / machine code. I'm not
entirely sure whether it would be permitted to merge functions with
different signatures, at least those cannot be compared without a cast.


Stefan

John Devereux

unread,
Dec 1, 2009, 1:50:17 PM12/1/09
to
D Yuniskis <not.goi...@seen.com> writes:

> John Devereux wrote:
>> D Yuniskis <not.goi...@seen.com> writes:
>>
>>> John Devereux wrote:
>>> You'll note that there are many embedded system targets that
>>> GCC doesn't support :> And, you have to understand the nature
>>> of the problem in order to know/suspect that you would want/need
>>> to deal with it (if so, why not just code it "right" in the
>>> first place?)
>>
>> I don't really disagree. But note that the ones GCC does not support -
>> i.e. some crufty old 8 & 16 bit parts - are precisely those that would
>> see no benefit anyway :)
>>
>> Right now there is still a good argument for this type of manual
>> optimisation, but this seems likely to disappear shortly IMO.
>
> I'm not looking at it as an optimization. That implies
> an after-the-fact activity. Rather, this is just a
> *coding style*. Something DECIDED UPON ahead of time
> (for one reason or another -- I've just illustrated a
> particular reason that influences that choice) and then
> applied consistently UNLESS IT SHOULDN'T BE.

OK, I see. Actually I can't recall using a 2 dimensional numeric array
on an embedded system. For some reason it has never arisen, so I have
not needed to think about it.

I often use arrays of strings, but these are explicitly arrays of
pointers to arrays of characters so the question does not arise.

>
> E.g., do you use:
>
> for (i = foo; i < bar; i++)
>
> or:
>
> for (i = foo; i < bar; ++i)
>
> as both are *functionally* equivalent. I suspect you
> CONSISTENTLY use one form, right? :>

Sure. The former, for no good reason I can recall, I expect it was in
K&R!.

--

John Devereux

Niklas Holsti

unread,
Dec 1, 2009, 1:49:25 PM12/1/09
to
Stefan Reuther wrote:

> Pertti Kellomaki wrote:
>> In fact, I would argue that compilers should be able to exploit
>> templating to reduce binary size. It is not unheard of to have
>> functions with identical algorithmic behavior but different
>> signatures. There may be compilers out there that aggressively
>> merge such functions if they result in the same machine code,
>> but clearly it would be more efficient to examine known instances
>> of a template rather than to examine all pairs of functions.
>
> Do these compilers/linkers actually exist?

I've seen C compilers that detect that the code at the end of one
function is identical to the code at the end of another, and avoid this
code duplication by making a cross-jump between the functions so that
they share their common ending code. This is a bit on the way towards
Pertti's suggestion. (However, I'm not sure if this was limited only to
shared compiler-generated function epilogues.)

There is also some work on "clone detection" from source code, which
IIRC at present aims mainly to find stolen code, or sloppy code that has
lots of copy-paste parts, but could eventually show up in compilers as a
code-size optimization.

Moving away from C/C++, some Ada compilers implement the Ada "generic"
feature, which roughly corresponds to C++ templates, by creating a
single copy of the generic code, parametrized with the instantiation
stuff (constants, variables, types, operations). Each instance of the
generic has its own parameter structure, but shares the code.

> Such a feature would be pretty hard to do standards-conformant anyway.
> In particular, the compiler may *not* merge functions of identical
> signature, because programmers can expect this to work
> void (*foo)() = func1;
> assert(foo != func2);
> even if func1 and func2 have the same body / machine code. I'm not
> entirely sure whether it would be permitted to merge functions with
> different signatures, at least those cannot be compared without a cast.

Merged functions could have different entry points, followed by a jump
to the shared code. Still a significant code reduction in most cases.

--
Niklas Holsti
Tidorum Ltd
niklas holsti tidorum fi
. @ .

D Yuniskis

unread,
Dec 1, 2009, 2:24:19 PM12/1/09
to
John Devereux wrote:

[attributions elided]

>>> Right now there is still a good argument for this type of manual
>>> optimisation, but this seems likely to disappear shortly IMO.
>>
>> I'm not looking at it as an optimization. That implies
>> an after-the-fact activity. Rather, this is just a
>> *coding style*. Something DECIDED UPON ahead of time
>> (for one reason or another -- I've just illustrated a
>> particular reason that influences that choice) and then
>> applied consistently UNLESS IT SHOULDN'T BE.
>
> OK, I see. Actually I can't recall using a 2 dimensional numeric array
> on an embedded system. For some reason it has never arisen, so I have
> not needed to think about it.

In many "desktop" applications, I have. So, instinctively apply
what I learned there to my embedded work. Easier (IMO) to
just "get into the habit" than to have to remember some little
thing lke this *later*. (brain fade :> )

> I often use arrays of strings, but these are explicitly arrays of
> pointers to arrays of characters so the question does not arise.
>
>> E.g., do you use:
>>
>> for (i = foo; i < bar; i++)
>>
>> or:
>>
>> for (i = foo; i < bar; ++i)
>>
>> as both are *functionally* equivalent. I suspect you
>> CONSISTENTLY use one form, right? :>
>
> Sure. The former, for no good reason I can recall, I expect it was in
> K&R!.

I use the latter. In this context, damn near any compiler should
be able to generate the same code. But, the prefix operator is
easier for (dumber) compilers to generate "better" code when
used in *other* contexts (e.g., x = foo[++i] vs. x = foo[i++]).
Again, it's just me forcing myself to use a style that will
ALL ELSE BEING EQUAL err in my favor.

Many of these style issues really "rub me the wrong way"
though I try hard to adhere to them. E.g., I *want* to
use the postfix operator but force the use of the infix
operator, instead.

Similarly, I break expressions so that operators *start*
the continuation line(s) rather than *end* the preceding
line despite the fact that it "feels" wrong. E.g.,

foo = bar
+ baz;

vs.

foo = bar +
baz;

<shrug> To each his own. I just think that consistency is
**really** important -- for yourself and those who follow.

D Yuniskis

unread,
Dec 1, 2009, 2:34:15 PM12/1/09
to
D Yuniskis wrote:
> Many of these style issues really "rub me the wrong way"
> though I try hard to adhere to them. E.g., I *want* to
> use the postfix operator but force the use of the infix
> operator, instead.

Grrrr... s/infix/prefix/ -- though that hopefully would have
been obvious. :<

Niklas Holsti

unread,
Dec 1, 2009, 2:44:54 PM12/1/09
to
D Yuniskis wrote:

> Similarly, I break expressions so that operators *start*
> the continuation line(s) rather than *end* the preceding
> line despite the fact that it "feels" wrong. E.g.,
>
> foo = bar
> + baz;
>
> vs.
>
> foo = bar +
> baz;

Seconded. I also like and use the former style: the operator goes on the
same line as the right-hand operand. Especially nice for sums where some
terms are added, other subtracted.

Luckily, this also "feels" right to me.

42Bastian Schick

unread,
Dec 1, 2009, 2:52:32 PM12/1/09
to
On Tue, 1 Dec 2009 09:32:00 -0000, "Bill Davy" <Bi...@SynectixLtd.com>
wrote:

>
>Embedded with cache? That's' really luxurious!


You're kinding ? Nearly all embedded PowerPCs have cache (types not
sold quantity, here the 565 and 555 likely outnumber all other
PowerPCs)
Also most higher ARM CPUs, ColdFire, SH ....

(Hey, people use a controller with cache and want a deterministic RTOS
;-P

Pertti Kellomaki

unread,
Dec 2, 2009, 2:37:11 AM12/2/09
to
Stefan Reuther wrote:
> So far, all schemes of template management I've encountered had their
> drawbacks in practice.

I don't doubt that, and I symphatize with your descriptions
of interaction between templates and the compiler.

> Templates are instantiated to generate code. Unless you're using
> templates that are completely inlined and optimized away (which is
> indeed useful, and which I use all the time), of course it affects the
> binary size.

Sure, but if you don't get the code from template instantiation,
then you need to get it from somewhere else, e.g. write it by hand.
If the template instantiation and the hand written code are equivalent,
then I would hope the resulting binaries to be equivalent. Otherwise
it would be time to change the compiler vendor ;-)

Some people (not you obviously) seem to think that there is some
intrinsic run time overhead using templates. But templates -- and
metaprogramming in a more general sense -- are really compile time
phenomena. Hence my comments.
--
Pertti

Stefan Reuther

unread,
Dec 2, 2009, 2:14:21 PM12/2/09
to
Pertti Kellomaki wrote:
> Stefan Reuther wrote:
>> So far, all schemes of template management I've encountered had their
>> drawbacks in practice.
>
> I don't doubt that, and I symphatize with your descriptions
> of interaction between templates and the compiler.
>
>> Templates are instantiated to generate code. Unless you're using
>> templates that are completely inlined and optimized away (which is
>> indeed useful, and which I use all the time), of course it affects the
>> binary size.
>
> Sure, but if you don't get the code from template instantiation,
> then you need to get it from somewhere else, e.g. write it by hand.

Upthread, Vladimir proposed to replace void pointer plus size by
templates for type safety. That is, essentially, replacing
memcpy(void* dest, const void* src, size_t size);
by, say,
template<typename T> copy(T dest[], const T src[], size_t nitems);
Normally, this will generate duplicate code.

Of course we could define the latter just as type-safe syntactic sugar
for memcpy,
inline template<typename T>
copy(T dest[], const T src[], size_t nitems)
{
memcpy(dest, src, nitems * sizeof(T));
}
but then it would qualify as a template which is optimized away, which I
excluded :-)

> If the template instantiation and the hand written code are equivalent,
> then I would hope the resulting binaries to be equivalent. Otherwise
> it would be time to change the compiler vendor ;-)

The problem just ist that in C++, with templates in particular, it needs
work to avoid bloat. With C, it needs work to generate bloat, because
all code has to be written explicitly (unless you're using preprocessor
tricks outlawed by every sane coding style).

> Some people (not you obviously) seem to think that there is some
> intrinsic run time overhead using templates. But templates -- and
> metaprogramming in a more general sense -- are really compile time
> phenomena. Hence my comments.

(Footnote: I've been playing around with toy compilers since I was
15, so I think I know how a compiler works and thinks. Pretty useful
knowledge, should be spread wider during developers.)

But isn't "templates have no runtime costs" one of the the first lessons
taught? You're trading type-safety and number-of-instructions-per-call
for code size. At least, that was my impression when learning C++.
Probably to counter the old "hey, what do I need templates, I have a
TArray containing TObject* which works fine" and "my hand-written vector
of integers is faster than std::vector<int>" arguments.


Stefan

Stefan Reuther

unread,
Dec 2, 2009, 2:19:48 PM12/2/09
to
Niklas Holsti wrote:
> Stefan Reuther wrote:
>> Pertti Kellomaki wrote:
>>> In fact, I would argue that compilers should be able to exploit
>>> templating to reduce binary size. It is not unheard of to have
>>> functions with identical algorithmic behavior but different
>>> signatures. There may be compilers out there that aggressively
>>> merge such functions if they result in the same machine code,
>>> but clearly it would be more efficient to examine known instances
>>> of a template rather than to examine all pairs of functions.
>>
>> Do these compilers/linkers actually exist?
>
> I've seen C compilers that detect that the code at the end of one
> function is identical to the code at the end of another, and avoid this
> code duplication by making a cross-jump between the functions so that
> they share their common ending code.

Ah, okay.

> Moving away from C/C++, some Ada compilers implement the Ada "generic"
> feature, which roughly corresponds to C++ templates, by creating a
> single copy of the generic code, parametrized with the instantiation
> stuff (constants, variables, types, operations). Each instance of the
> generic has its own parameter structure, but shares the code.

When dealing with single objects, this can be approximated in C++ by
passing an object with virtual functions. For arrays, Ada (or
Haskell...) wins by not requiring an individual vptr for each object,
but just one for everything.

In its simplest form, Generics are just a type system feature, not a
runtime feature. You pass a pointer to an object in, you get a pointer
to an object of the same type out, the code inbetween doesn't look at
it. If I recall correctly, that's how Java does it. I'd love to see that
in C++.


Stefan

0 new messages