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
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 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.
> 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 *
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.
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
That depends on the circumstances, but if you mean that gratuitous
typedefs make the code less clear, then I agree.
> 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..
}
> 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.
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.
> > 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?
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.
>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 !
> 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
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.
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
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.
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.
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)
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
<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.
>>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
That was pretty obvious, and mostly true for any random access.
Does mean to imply that embedded processors don't have any cache? :-)
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?
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
However even gcc is able to optimize this away AIUI (gcc 4.4+, see
-f-loop-*, e.g. -floop-interchange).
--
John Devereux
>>> 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?"
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!
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
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
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? :>
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 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
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
. @ .
[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.
Grrrr... s/infix/prefix/ -- though that hopefully would have
been obvious. :<
> 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.
>
>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
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
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
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