-Wconversion-null does not take all constructors into account - c++

Upgrading some legacy code, I've started getting "warning: passing NULL to non-pointer argument 1 of ‘Identifier::Identifier(int)’ [-Wconversion-null]" messages from g++. Mostly this is good, but it doesn't seem to take classes with multiple constructors into account.
For example, take this test code:
#include <stdio.h>
class Identifier
{
private:
int m_iID;
public:
Identifier(const char* p_c) { m_iID = p_c ? p_c[0] : 0; }
Identifier(int i) { m_iID = i; }
};
int main(int argc, char* argv[])
{
Identifier* p_ID = new Identifier(NULL);
return 0;
}
(Please ignore what the constructors actually do, this is just an illustration of the issue. For context, the code in question is a class that stores an identifier in the form of a hash value. The constructors can either take a string to convert to a hash, or the hash value directly.)
The "new" statement on the third to last line throws this warning. The thing that is puzzling me is that g++ is apparently assuming that it needs to use the Identifier(int i) constructor, and ignores the Identifier(const char* p_c) constructor, which is a pointer.
Note that changing the ints to unsigned ints causes an ambiguity error instead, this being a 32-bit system.
I know specifying -Wno-conversion-null would fix the problem, as would passing 0 or explicitly casting NULL to const char*. But I'm curious as to why the seemingly valid constructor is being ignored. Plus, I'd like to avoid a massive search-and-replace job whilst keeping the warning active.

At least on this CentOS box, NULL is defined in linux/stddef.h:
#undef NULL
#if defined(__cplusplus)
#define NULL 0
#else
#define NULL ((void *)0)
#endif
As such, NULL in C++ is an integer; therefore the compiler chooses the int constructor by default and gives you the warning you're seeing.

Related

Why is there no warning or error when initializing a const pointer to null?

This might be a very basic question, but I tried to find the answer in SO and couldn't find the exact answer to it.
What's the point of initializing a const pointer with nullptr?
int *const pi = nullptr;
Why does the compiler not emit a warning or error, seeing how pointer pi can't be used effectively anywhere in the program?
I have tried compiling this with g++ -w.
Also, Can you give some use case where a const pointer will get initialized to nullptr for a valid purpose in real code?
Do not confuse const, which means: "The values cannot change after initialization" with "this value is always, under all circumstances a nullptr". What I mean is this:
struct foo {
int *const pi = nullptr;
foo( int* p) : pi(p) {}
};
Different instances of foo can have different values for pi.
Another way to have the initialization conditional is:
int *const pi = (some_condition) ? nullptr : some_pointer;
Even with a plain
int *const pi = nullptr;
there is nothing wrong. It is basically giving a different name to nullptr, so you can use pi anywhere where you can use a nullptr. For example as sentinel in a container:
int * const empty = nullptr;
for ( int* element : container) {
if (element != empty) do_something(element);
}
This might be considered obfuscation, and directly using nullptr would be more readable. Though consider the sentinel value changes later on, then using empty makes is simple to replace all occurences of it.
I'll try to generalize #AdrianMole's (second) example:
Sometimes, a piece of code is a degenerate case or an outlier case of a more general instruction or pattern. Thus:
In #AdrianMole's example, there is an out-of-source parameter which controls whether pi should have a meaningful value or not. You don't want to twist your source up too much in order to accommodate this scenario, so you keep the same code but use nullptr, likely with a later (run-time) check for whether you have a nullptr or not.
You might have a template class where the general definition is something like:
template <typename T>
class A {
int *const pi = whatever<T>();
}
with a specialization
template <>
class A<foo_t> {
int *const pi = nullptr;
}
because you want to avoid calling whatever<foo>().
It is likely that such code can be improved to avoid the explicit nullptr assignment. But it would be a bit excessive to push developers into doing this through a warning.
(An error is out of the question since it's perfectly valid code in terms of the language standard.)
Although I agree that initializing a const pointer to nullptr isn't generally especially useful, one situation where it may me appropriate is where you would conditionally define the const pi pointer to either nullptr or some other (valid) address, depending on a compile-time setting, like in the following example. (The const qualifier prevents other code from inadvertently changing the value and, thusly, breaking the aforementioned compile-time condition.)
#include<iostream>
#define USENULL 1 // Comment out this line to get a 'valid' pointer!
int main()
{
int a = 42;
#ifdef USENULL
int* const pi = nullptr;
#else
int* const pi = &a;
#endif
int* pa = pi;
if (pa) std::cout << *pa;
else std::cout << "null pointer";
std::cout << std::endl;
return 0;
}
Such a situation could arise when you need to differentiate between debug and release builds, for example.

Array of Structs with an enum member causes error when giving the array a NULL termination, but no error when not doing so

I've tried searching for this, but I've not found what causes this strange behavior, there must be something I'm missing but I can't tell what it is:
The problem is the following i have the following struct:
struct s_log_message{
lll level;
const unsigned int i;
const char *msg;
}typedef log_message;
and the following array of said structs:
const log_message logging_messages[]{
{
error,
0,
ACR "ERROR" ACS ":" "No arguments given, use -h or --help for information on how to use the program\0"
},
NULL
};
ACR and ACS are defines to change terminal color.
lll level is an enum:
enum log_level_list{none=0,info=1,warn=2,error=3};
typedef enum log_level_list lll;
All this code is in 3 different files:
log.h:
#ifndef LOG
#define LOG 220
enum log_level_list{none=0,info=1,warn=2,error=3};
typedef enum log_level_list lll;
class log{
public:
static void set_log_level(lll val);
static unsigned char get_log_level(void);
static void l(unsigned int warn_i);
private:
log(){}
static unsigned char log_level;
};
#endif
lmg.h:
#ifndef LOG_MESSAGES
#define LOG_MESSAGES 100
#include "../lib/log.h" //included for definition of enum type lll
struct s_log_message{
lll level;
const unsigned int i;
const char *msg;
}typedef log_message;
extern const log_message logging_messages[];
#endif
and lmg.cpp:
#include "../lib/col.h" //terminal color defines
#include "../lib/lmg.h" //header for this file
#include <cstddef> //included for definition of NULL
const log_message logging_messages[]{
{
error,
0,
ACR "ERROR" ACS ":" "No arguments given, use -h or --help for information on how to use the program\0"
},
NULL
};
To recap the problem is that terminating the loggin_messages[] array with NULL causes the compiler o throw the following error:
g++ -Wall -o DFHM ./src/main.cpp ./lib/lib.cpp ./lib/EasyBMP.cpp ./lib/dat.cpp ./lib/log.cpp ./lib/lmg.cpp
./lib/lmg.cpp:12:1: error: invalid conversion from ‘long int’ to ‘lll’ {aka ‘log_level_list’} [-fpermissive]
12 | };
| ^
| |
| long int
make: *** [makefile:14: main] Error 1
I'm aware that spreading so little code among so many files is counterproductive, I'm just trying to get used to doing that so that if ever i get into larger projects I'll be accustomed, and to practice of course.
NULL is a null pointer constant. It's intended to set pointers to a null, not-pointing-to-anything, value.
logging_messages doesn't hold pointers though. It's an array of log_message objects. It doesn't make any sense to initialize a log_message to NULL.
If you want to add an "empty" object to serve as a terminator to your array instead of storing the array's size separately, you can do that, but your type's "empty" state is up to you. For instance { none, 0, nullptr } could be your defined "empty" state. That particular state could also be written as simply {}, since members that aren't given in aggregate initialization will be value-initialized.
You don't need to terminate every kind of array with NULL. In fact, strictly speaking not even char arrays need to be terminated with NULL. You can happily have non-NULL terminated arrays as long as you write your code so that you don't go beyond the end of the array.
Of course with char arrays there is an established convention such that many functions interpret a NULL valued char as the end of useful data. However that idea isn't established for any other type of array.
Or to say it a different way, NULL isn't a valid value for a struct (or a class, etc). While it is for a pointer to one, that's a completely different type. When you have an actual instance of a thing (such as a struct) it cannot be NULL.

C++ equivalent to taking address of a constant?

I've got some code written in C (specifically targeted to gcc) that needs to also compile as C++ (g++). I've run into a construct I'm having difficulty dealing with.
There are some macros used in a lot of places which basically take the address of a constant, although they also work on non-constants. The construct winds up looking something like this:
int *iptr = (&(int){5});
What this essentially lets happen is a constant be specified, and then a pointer to that constant can be taken for use in a function that requires an int pointer, but only a constant is specified. Multiple values can also be specified inside the curley braces to construct an actual temporary array, too. And it works great.
Problem in, g++ does not like this at all, throwing
error: non-lvalue in unary `&'
Update: Looks like I can take the address of a constant in C++ by doing something like:
const int & ref = 5;
So that's the starting point for the direction I'm trying to take.
The following appears to work in both C and C++, although it only compiles under C++11, not C++03.
#ifdef __cplusplus
#define A(type,x) (&(const type &)(type)x)
#define Of(...) { __VA_ARGS__ }
#else
#define A(type,x) (&(type){x})
#define Of(...) __VA_ARGS__
#endif
Test Code:
int test(const void *ptr,int len) {
char *str = (char *)ptr;
int i;
for (i=0;i<len;i++) {
printf("%02X ",str[i]);
}
printf("\n");
return 0;
}
int main() {
test(A(int,5),4);
test(A(x,Of(5,6)),8);
int i=1,j=2;
test(A(x,Of(i,j)),8);
}
Edit: Whoops, not quite there. Doesn't work if the type is a pointer!
test(A(char *,9),4);
=>
error: invalid cast of an rvalue expression of type 'char*' to type 'const char*&'
Note: I'm abandoning this approach in favor of a different approach (compiling C code as C and linking it via C++) but will keep this answer up in case it is useful to somebody.

Determine if same pointers are passed to a macro

There are set of macros, for debugging, logging, stack-trace displaying etc. One of them is like:
#define ASSERT_IF_NULL_2(_ptr1, _ptr2) \
ASSERT(_ptr1); \
ASSERT(_ptr2);
This is over simplified version of macro(s) I have written. I have custom-assertion dialog box if assertion (runtime) fails, logs such null-check failure into log file. Macros are also written such that non-pointers are checked at compile time (static-assert).
Now, I am looking for some static-assert to check if two pointers are actually same. Example:
int* ptr;
ASSERT_IF_NULL_2(ptr, ptr);
Should raise a compiler error since both arguments to macro are same. I don't care if pointers point to same memory (since that thing is runtime).
I have tried expression like:
int xx;
xx = 1 / (ptr-ptr);
xx = 1 / (&ptr - &ptr);
None of them gives divide-by-zero compiler error.
Also, I have tried with template that takes void* is template argument:
template<void* T>
class Sample{};
But it doesn't allow local-variable pointers to be passed to template non-type argument.
I am using VC9, which doesn't support constexpr keyword (even VS2012 doesn't). I tried using 'const' instead, which doesnt throw error. I also used the expression as array-size, which always results in error.
int array[(&ptr - &ptr)]; // Even with ptrdiff_t
Alright, the solution turns out to be simple. Use static_assert and Stringizing Operator (#):
#define ASSERT_TWO(a,b) static_assert((void*)#a != (void*) #b, "'" #a "' and '" #b "' are same variables passed to ASSERT_TWO.")
Demo:
int *global;
int main()
{
int *local;
ASSERT_TWO(global, local); //should pass
ASSERT_TWO(local, global); //should pass
ASSERT_TWO(global, global); //should fail
ASSERT_TWO(local, local); //should fail
(void)local; //suppress unsused warning!
}
which results in very helpful error message:
main.cpp: In function 'int main()':
main.cpp:18:5: error: static assertion failed: 'global' and 'global' are same variables passed to ASSERT_TWO.
main.cpp:19:5: error: static assertion failed: 'local' and 'local' are same variables passed to ASSERT_TWO.
Online demo.
Hope that helps.
I recall that this will not help you, as you're using VC9, but I keep this answer as it might help others. As of now, you could use other solution which generates redeclaration error which is less helpful error message compared to the static_assert message.
So you want to check if same name is passed to both arguments, right? Then this simple trick works
#define ASSERT_IF_NULL_2(_ptr1, _ptr2)\
{\
int _check_##_ptr1;\
int _check_##_ptr2;\
}\
ASSERT(_ptr1);\
ASSERT(_ptr2);
int main ()
{
int* ptr1;
int* ptr2;
ASSERT_IF_NULL_2 (ptr1, ptr1); // error: redeclaration of `int _check_ptr1'
ASSERT_IF_NULL_2 (ptr1, ptr2); // OK
}
EDIT (By OP, Ajay):
On Visual C++ compiler, we can use MS specific keyword __if_exists to give error through static-assert :
#define ASSERT_IF_NULL_2(_ptr1, _ptr2)\
{\
int _check_##_ptr1;\
__if_exists(_check_##_ptr2)
STATIC_ASSERT(false, "Same pointer passed twice")
}\
Since static_assert keyword doesnt exist for pre-VS2010 compilers, one may use custom-written STATIC_ASSERT.
Are you trying to check if the pointers point to the same memory or if they point at the same value?
In the first case
xx = 1 / (ptr-ptr);
should work.
in the second case
xx = 1 / (*ptr-*ptr);
should work.
Try one of these maybe:
ASSERT_IF_NULL(ptr1 == ptr1);
ASSERT(ptr1 != ptr2)
As pointed outby Nawaz you want a compile error so try this:
1/(ptr1 != ptr2);
1/static_cast<int>(ptr1 != ptr2);
static_assert(ptr1 != ptr2, "Pointers are different") //if you dont use c++0x look here:http://stackoverflow.com/a/1664651/258418

memset used with zero length parameter: ignore or watch out?

I'm having trouble finding any information regarding the following warning when linking a dynamic library:
In function `MyClass::myfunc()':
MyClass.cpp:(.text+0x14e4): warning: memset used with constant zero length parameter; this could be due to transposed parameters
Here is an excerpt of myfunc:
void MyClass::myfunc() {
vector<Variable*>::const_iterator it;
for (it = m_vars.begin();
it != m_vars.end();
++it) {
if ((*it)->recordme) {
MyRecord* r = new MyRecord(*it);
initMyRecord(*r);
m_records.push_back(r);
}
}
}
So I'm pretty much stuck on were should I be looking for possible causes for this memset. The call to the new operator is my first suspect, but I'm not even sure if it's worth looking for this. I'm not sure either if I should take this warning seriously or let it pass.
Question: what should I do about this warning? And what kind of patterns should I look out for in order to assure that I'm not going to shoot myself in the foot later?
Update:
Here is the MyRecord constructor, which is in a header file, so it might or might not be inlined, if I understand correctly.
class MyRecord {
public:
MyRecord(const Variable* var) :
buffer(0),
lastSave(-1 * std::numeric_limits<double>::max()),
sample(100),
bufsize(100),
gv(var),
rec_function(0)
{};
virtual ~Record() {
if (rec_function)
delete rec_function;
rec_function = 0;
};
private:
Record(const Record&);
Record& operator=(const Record& rec);
public: // #todo: remove publicness
boost::circular_buffer< boost::tuple<double,boost::any> > buffer;
double lastSave;
double sample;
unsigned int bufsize;
const Variable* gv;
RecordFunctor* rec_function;
};
The RecordFunctor is a pure-virtual struct:
struct RecordFunctor {
virtual ~RecordFunctor() {};
virtual void record(const double) = 0;
};
Additional info? I'm compiling with flags -O2 and g++ (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1
The behavior of the memset() function with a size of 0 is well defined, as long as the pointer argument is valid.
See section 7.21.1 of the C99 standard, or 7.24.1 of the C11 standard:
Where an argument declared as size_t n specifies the length of the
array for a function, n can have the value zero on a call to that
function.
On the other hand, the warning is a sensible one; a call like memset(s, 0, 0) is not dangerous, but it's not useful (it does nothing), and could easily indicate a programming error.
Greg's answer explains how to avoid it in this case.
You are calling the boost::circular_buffer constructor with a capacity of 0. This probably causes that constructor to call memset() to initialise the storage used by the circular buffer, but you've told it you want a buffer of zero size.
The solution is to give the circular_buffer constructor the actual size you want, not zero (zero doesn't make sense).