Strict aliasing of array vs std::array - c++

This problem depends on the GCC verion used. On 5.4.0, at least, the following code will fail:
#include <iostream>
#include <iomanip>
#include <array>
int main() {
uint8_t buf[12] = {0};
std::array<uint8_t, 12> array{0};
uint16_t data = 0x5511;
*(reinterpret_cast<uint16_t*>(&buf[1])) = data;
*(reinterpret_cast<uint16_t*>(&array[1])) = data;
std::cout << __VERSION__ << std::endl;
}
During cmpilation with this error:
source_file.cpp: In function ‘int main()’:
source_file.cpp:27:43: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
*(reinterpret_cast<uint16_t*>(&buf[1])) = data;
I understand the concern with the aliasing. However, there are two questions:
Why does the next line compies fine? Why is the compiler OK with std::array and not with the regular C buffer?
This error doesn't occur in the newer compiler (above 7.2). Anyone knows why?

Related

Why can char be initialized to nullptr in a std::array, but not by itself?

This code doesn't compile (using gcc 9.3)...
int main() {
char bar = nullptr; //error: cannot convert ‘std::nullptr_t’ to ‘char’ in initialization
}
But this code does compile...
#include <array>
int main() {
std::array<char, 1> foo = {nullptr}; // foo[0] == char(0), why?
}
Why is there a distinction?
Why can char be initialized to nullptr in a std::array
It can't. The shown program is ill-formed in C++.
When an ill-formed program compiles, there are typically two possibilities:
It is a language extension.
It is a compiler bug.
In this case, I think it is the latter. The bug reproduces in GCC 9, but appears to have been fixed in GCC 10.

What are the strict aliasing rules when casting *from* a char array?

I'm confused by the strict aliasing rules when it comes to casting a char array to other types. I know that it is permitted to cast any object to a char array, but I'm not sure what happens the other way around.
Take a look at this:
#include <type_traits>
using namespace std;
struct{
alignas (int) char buf[sizeof(int)]; //correct?
} buf1;
alignas(int) char buf2[sizeof(int)]; //incorrect?
struct{
float f; //obviously incorrect
} buf3;
typename std::aligned_storage<sizeof(int), alignof(int)>::type buf4; //obviously correct
int main()
{
reinterpret_cast<int&>(buf1) = 1;
*reinterpret_cast<int*>(buf2) = 1;
reinterpret_cast<int&>(buf3) = 1;
reinterpret_cast<int&>(buf4) = 1;
}
Compiling using g++-5.3.0 results in warnings only on the second and third line of main:
$ g++ -fsyntax-only -O3 -std=c++14 -Wall main.cpp
main.cpp: In function ‘int main()’:
main.cpp:25:30: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
*reinterpret_cast<int*>(buf2) = 1;
^
main.cpp:26:29: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
reinterpret_cast<int&>(buf3) = 1;
^
Is gcc correct in that lines 1 and 4 are correct, while lines 2 and 3 are not? I'm fairly sure line 4 is correct (that's what aligned_storage is for), but what are the rules at play here?
First of all, absence of warning is not a guarantee of correctness! gcc is getting better and better at spotting problematic code, but it is still not a static analyzing tool (and those are not perfect either!)
Second of all, yes, you are not allowed to access char array through a pointer to other type.

Why does g++ 4.8.1 issue a conversion warning

When I compile the code below with g++ 4.8.1 (64bit) in this way:
$ g++ -Wconversion -o main main.cpp
I get this result:
main.cpp: In function ‘int main()’:
main.cpp:12:20: warning: conversion to ‘int’ from ‘long unsigned int’ may alter its value [-Wconversion]
int i = sizeof(x)/sizeof(x[0]);
^
My expectation would be that the compiler should be able to evaluate the expression at compile time. If you make a similar program in plain c, gcc works like a charm.
Should this be considered a bug in g++ (e.g. clang++ does not have this problem)?
if you change the problematic line to something like:
char c = 0x10000000/0x1000000;
then the compiler does not complain. This suggest that some constant evaluation is done before warning generation.
main.cpp:
#include <iostream>
struct foo {
int a;
int b;
};
foo x[50];
int main()
{
int i = sizeof(x)/sizeof(x[0]);
std::cout << i << std::endl;
return 0;
}
int i = sizeof(x)/sizeof(x[0]);
//int <-- std::size_t <-- std::size_t / std::size_t
The type of the expression sizeof(x)/sizeof(x[0]) is std::size_t which on your machine is unsigned long int. So conversion from this type to int is data-loss, if the source is bigger in size than the target.
Though, I agree that in your case, there would not be actual data-loss if the compiler actually computes the value, but I guess it applies -Wconversion before the actual computation.
sizeof() returns you std::size_t not int! So cast it or declare i as std::size_t.
std::size_t i = sizeof(x)/sizeof(x[0]);

GCC Warns About Function Pointer to Object Pointer Cast

Clearly casting between function pointers and object pointers is undefined behaviour in the general sense, but POSIX (see: dlsym) and WinAPI (see: GetProcAddress) require this.
Given this, and given the fact that such code is targeting a platform-specific API anyway, its portability to platforms where function pointers and object pointers aren't compatible is really irrelevant.
But -Wpedantic warns about it anyway, and #pragma GCC diagnostic ignored "-Wpedantic" has no effect:
warning: ISO C++ forbids casting between pointer-to-function and pointer-to-object [enabled by default]
I want to keep -Wpedantic enabled, since it does give good warnings, but I don't want to have real warnings and errors lost amidst a sea of irrelevant warnings about function pointer to object pointer casts.
Is there a way to accomplish this?
Running GCC 4.8.0 on Windows (MinGW):
gcc (rubenvb-4.8.0) 4.8.0
CODE SAMPLE
#include <windows.h>
#include <iostream>
int main (void) {
std::cout << *reinterpret_cast<int *>(GetProcAddress(LoadLibraryA("test.dll"),"five")) << std::endl;
}
Emits (with -Wpedantic):
warning_demo.cpp: In function 'int main()':
warning_demo.cpp:7:87: warning: ISO C++ forbids casting between pointer-to-funct
ion and pointer-to-object [enabled by default]
std::cout << *reinterpret_cast<int *>(GetProcAddress(LoadLibraryA("test.dll"),
"five")) << std::endl;
^
I think you could use g++'s system_header directive here:
wrap_GetProcAddress.h:
#ifndef wrap_GetProcAddress_included
#define wrap_GetProcAddress_included
#pragma GCC system_header
template <typename Result>
Result GetProcAddressAs( [normal parameters] )
{
return reinterpret_cast<Result>(GetProcAddressAs( [normal parameters] ));
}
#endif
This works fine.
template <typename RESULT, typename ...ARGS>
void * make_void_ptr(RESULT (*p)(ARGS...)) {
static_assert(sizeof(void *) == sizeof(void (*)(void)),
"object pointer and function pointer sizes must equal");
void *q = &p;
return *static_cast<void **>(q);
}
There's always the memcpy trick you can use:
int (*f)() = 0;
int *o;
memcpy(&o, &f, sizeof(int*));
You can see it on ideone: m is generating warnings, while g is OK.
As to other course of action you might want to take: One obvious possibility would be to "fix" the header defining dlsym to actually return a function pointer (like void (*)()). Good luck with that.

Strict aliasing and std::array vs C-style array

When compiling the following code with gcc 4.7 (g++-mp-4.7 (GCC) 4.7.0 built with MacPorts on OS X) I get seemingly contradictory results.
The compiler does not complain when I try to reinterpret and dereference a section of an std::array as an uint32_t but it does when using a C-style array.
Example code:
#include <array>
#include <cstdint>
int main() {
std::array<uint8_t, 6> stdarr;
*reinterpret_cast<uint32_t*>(&stdarr[0]) = 0; // OK
uint8_t arr[6];
*reinterpret_cast<uint32_t*>(&arr[0]) = 0;
// ^ error: dereferencing type-punned pointer will break strict-aliasing rules [-Werror=strict-aliasing]
}
Compiler command is:
$ g++ -o test -std=c++0x -Wall -Wextra -Werror main.cpp
Why are they treated differently?
When taking the address of the std::array, the expression arr[0] is equivalent to the function call arr.operator[](0) which returns a reference, rather than the pointer arithmetic expression (arr + 0). Perhaps the compiler does not attempt to "see through" the operator[] function call when generating aliasing warnings.