James said:
James Kanze wrote:
Does the C++ standard define what happens when the size
argument of void* operator new(size_t size) cannot represent
the total number of bytes to be allocated?
For example:
struct S
{
char a[64];
};
S* allocate(int size)
{
return new S[size]; // What happens here?
}
int main()
{
allocate(0x7FFFFFFF);
}
Supposing that all values in an int can be represented in a
size_t (i.e. that size_t is unsigned int or larger---very, very
probably), then you should either get the memory, or get a
bad_alloc exception (which you don't catch). That's according
to the standard; a lot of implementations seem to have bugs
here.
I think, you are missing a twist that the OP has hidden within
his posting: the size of S is at least 64. The number of S
objects that he requests is close to
numeric_limits<size_t>::max().
It's not on the systems I usually use, but that's not the point.
So when new S[size] is translated into raw memory allocation,
the number of bytes (not the number of S objects) requested
might exceed numeric_limits<size_t>::max().
And? That's the implementation's problem, not mine. I don't
see anything in the standard which authorizes special behavior
in this case.
The question is what behavior is "special". I do not see which
behavior the standard requires in this case.
I agree that it's not as clear as it could be, but the standard
says that "A new-expression passes the amount of space requested
to the allocation function as the first argument of type std::
size_t." That's clear enough (and doesn't talk about
arithmetic; how the compiler knows how much to allocate is an
implementation detail, as long as it gets it right). The
problem is what happens when the "amount of space" cannot be
represented in a size_t; the standard seems to ignore this case,
but since it is clear that the requested allocation can't be
honored, the only reasonable interpretation is that the code
behave as if the requested allocation can't be honored: throw a
bad_alloc, unless the operator new function is nothrow, in which
case return a null pointer.
I think (based on my understanding of [5.3.4/12]) that in such
a case, the unsigned arithmetic will just silently overflow
and you end up allocating a probably unexpected amount of
memory.
Could you please point to something in §5.3.4/12 (or elsewhere)
that says anything about "unsigned arithmetic".
I qualified my statement by "I think" simply because the
standard is vague to me. However, it says for instance
new T[5] results in a call of operator new[](sizeof(T)*5+x),
and operator new takes its argument at std::size_t. Now,
whenever any arithmetic type is converted to std::size_t, I
would expect [4.7/2] to apply since size_t is unsigned. When
the standard does not say that usual conversion rules do not
apply in the evaluation of the expression
Note that code is part of a non-normative example, designed to
show one particular aspect, and not to be used as a normative
implementation.
That the example is concerned about showing the fact that the
requested space may be larger than simply sizeof(T)*5, and
doesn't bother with other issues
.
It gives the formula above. It does not really matter whether
you interpret
as unsigned arithmetic or as plain math. A conversion to
std::size_t has to happen at some point because of the
signature of the allocation function. If [4.7/2] is not meant
to apply to that conversion, the standard should say that
somewhere.
(It is a bit vague, I'll admit, since it says "A
new-expression passes the amount of space requested to the
allocation function as the first argument of type std::
size_t." It doesn't really say what happens if the "amount
of space" isn't representable in a size_t.
So you see: taken litterally, the standard guarantees
something impossible to happen.
More or less. And since the compiler can't honor impossible
requests, the request must fail somehow. The question is how:
undefined behavior or something defined? In the case of
operator new, the language has specified a defined behavior for
cases where the request fails.
There are two ways to interpret this: at least one school claims
that if the system cannot honor your request, you've exceeded
its resource limit, and so undefined behavior ensues. While the
standard says you must get a bad_alloc, it's not really required
because of this undefined behavior. This logic has often been
presented as a justification of lazy commit. (Note that from
the user point of view, the results of overflow here or lazy
commit are pretty much the same: you get an apparently valid
pointer back, and then core dump when you try to access the
allocated memory.)
Note that the problem is more general. Given something like:
struct S { char c[ SIZE_MAX / 4 ] ; } ;
std::vector< S > v( 2 ) ;
v.at( 4 ) ;
am I guaranteed to get an exception? (Supposing that I didn't
get a bad_alloc in the constructor of v.)
Hm, that is a mixure of common sense and wishfull thinking
Maybe
. I think that the wording of the standard here is
vague enough that you have to use common sense to interpret it.
In some ways, the problem is similar to that of what happens to
the allocated memory if the constructor in a new expression
throws. The ARM didn't specify clearly, but "common sense" says
that the compiler must free it. Most implementations ignored
common sense, but when put to the point, the committee clarified
the issue in the direction of common sense.
I agree that a bad_alloc is clearly what I would _want_ to
get. I do not see, however, how to argue from the wording of
the standard that I _will_ get that.
The absense of any specific liberty to do otherwise?