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
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.
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.
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]);
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.
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.