On 06/02/2020 23:23, pozz wrote:
> Il 06/02/2020 21:41, David Brown ha scritto:
>> On 06/02/2020 16:06, pozz wrote:
>>> Il 06/02/2020 14:58, David Brown ha scritto:
>>
>>>
>>>> nor can you implement memcpy
>>>> or memmove, due to the type aliasing and effective type rules.� If you
>>>> want to be sure of problem-free code that is safe regardless of
>>>> optimisation, link-time optimisation, new generations of compilers,
>>>> etc., then you'll be quite careful and make good use of gcc attributes.
>>>
>>> What about copying byte by byte?
>>> Here[1] you can see newlib memcpy implementation. If
>>> PREFER_SIZE_OVER_SPEED or __OPTIMIZE_SIZE__ is defined, the
>>> implementation is really copying byte by byte.
>>>
>>> I don't really know how newlib used by my compiler (CubeIDE from ST)
>>> was compiled, maybe I'm using dumb version of memcpy already.
>>>
>>> This is an extract from a listing:
>>>
>>> 08025850 <memcpy>:
>>> ��8025850:��� b510��������� push��� {r4, lr}
>>> ��8025852:��� 1e43��������� subs��� r3, r0, #1
>>> ��8025854:��� 440a��������� add��� r2, r1
>>> ��8025856:��� 4291��������� cmp��� r1, r2
>>> ��8025858:��� d100��������� bne.n��� 802585c <memcpy+0xc>
>>> ��802585a:��� bd10��������� pop��� {r4, pc}
>>> ��802585c:��� f811 4b01���� ldrb.w��� r4, [r1], #1
>>> ��8025860:��� f803 4f01���� strb.w��� r4, [r3, #1]!
>>> ��8025864:��� e7f7��������� b.n��� 8025856 <memcpy+0x6>
>>>
>>> I'm not an expert of assembly, but it seems to me it is implemented
>>> in the simple and not optimized way.
>>>
>>
>> It is not the actual copying that is the problem - copying by char is
>> simple and safe (though often inefficient).� The issue is that the C
>> standards say memcpy also copies the effective type in certain
>> circumstances - there is no way to specify that in C, and it is
>> therefore a special feature of the library memcpy.�
>
> Could you make an example? I didn't understand.
Suppose you have a block "b" of memory allocated on the heap with malloc
- it has no "declared type" because it was not part of a C-defined
object. You are free to store data of any kind in "b", and it takes on
a type based on the access you used to store to "b" (unless you use
character type access, which leaves it untyped). Let's say you treat
"b" as an array of floats and fill it up - now its effective type is
float[].
Suppose you have another C object or array "s" with a specific type from
somewhere - such as an array of char* pointers.
You want to copy the contents of "s" into "b".
You could do this in several ways:
1. Read from "s" as char* pointers, converting to a float using a union,
and write it to "b". Then "b" is still an array of floats and the
compiler knows that any access to it as an array of char* pointers is
undefined behaviour - it can assume it can't happen. Beware the nasal
demons!
2. Make a pointer "char* * p = (char* *) b" and use that as the
destination when copying from "s" to "b". The compiler knows that "b"
is now an array char* pointers, and can be accessed as such. Everything
works, but you need a specific copying function each time.
3. Make a generic function that copies using unsigned char, and call
that to copy from s to b. Then b takes on the effective type of s, and
so b is now an array of char* pointers. Everything works, but copying
is inefficient.
4. Make a generic function that copies using uint32_t for speed, and
call that to copy from s to b. Then b becomes an array of uint32_t, and
accessing it as an array of char* pointers is undefined behaviour.
Nasal demons again.
5. Call the standard library memcpy. Then b gets the effective type of
s, and everything works. This is true whether the compiler generates a
local loop, or calls the library function, and it is true whether the
copying is done by byte or in larger lumps. The library memcpy is
special here - you cannot duplicate that behaviour in standard C.
This kind of thing - type based alias analysis and the effective type
rules in C - is difficult to get right. And it is not often that the
compiler can use this extra information for optimisation. But sometimes
it can. And sometimes it uses it for an optimisation that is correct
according to the C code you wrote, but not according to what you wanted.
Understanding the rules is hard, and sometimes playing by the rules is
even harder, so one solution is to change the rules. The
"-fno-strict-aliasing" flag in gcc changes the semantics of C to say
that the effective type of an object is always the type used to access
it - this simplifies things a lot here, at the cost of occasionally
missed optimisation opportunities. For example, the Linux kernel is
always compiled with "-fno-strict-aliasing".
(Note that this flag does not help with the aliasing issue with
home-made malloc, as that's a different thing entirely.)
>
> Anyway as you can see, newlib just implements memcpy in pure C language
> when compiled without optimizations. Are you saying it's bugged?
No - it can be treated as special because it is the standard library for
your implementation.
>
>
>> A homemade memcpy does not have that same feature.� (In a similar
>> vain, there is no way to get memory in standard C that has "no
>> declared type" except via the library malloc and friends - a homemade
>> malloc won't do.)� I am not sure what the best solution is here.
>>
>> Anyway, for memcpy make sure the compiler can use the builtin versions
>> where possible (avoid -ffreestanding, or use -fbuiltin) as this will
>> give far better code.
>>
>
> I don't use -ffreestanding, but I don't know if I'm using -fbuiltin.
> Anyway you are suggesting to use builtin functions that are functions
> built *in* the compiler and not in the newlib.
>
> Another reason to consider useless newlib.
>
When you use one of the common "small" functions in the C standard
library, like memcpy, memset, strcat, etc., the compiler knows what they
do without knowing the source. If it can make smaller or faster code
inline with the same effect as specified in the standards, then it may
do so. Typically for memcpy that means the compiler knows the size of
the copy and the alignments at compiler time. For example:
uint32_t rawfloat(float f) {
uint32_t u;
memcpy(&u, &f, sizeof(u));
return u;
}
This will be turned into a register move (if needed, depending on the
cpu), with nothing stored in memory and no library calls made. And
unlike faffing around with pointer casts, it is correct C code. And
unlike using a type-punning union, it is correct C++ code as well as
correct C code.
But more general calls to memcpy will be passed on to the library function.