Mã sau (thực hiện số học con trỏ trên các ranh giới con) có hành vi được xác định rõ ràng cho các loại T
mà nó biên dịch (trong đó, trong C++ 11, does not not necessarily have to be POD) hoặc bất kỳ tập con nào không?Số học con trỏ trên các ranh giới subobject
#include <cassert>
#include <cstddef>
template<typename T>
struct Base
{
// ensure alignment
union
{
T initial;
char begin;
};
};
template<typename T, size_t N>
struct Derived : public Base<T>
{
T rest[N - 1];
char end;
};
int main()
{
Derived<float, 10> d;
assert(&d.rest[9] - &d.initial == 10);
assert(&d.end - &d.begin == sizeof(float) * 10);
return 0;
}
LLVM sử dụng một biến thể của kỹ thuật nói trên trong việc thực hiện một loại vector nội bộ được tối ưu hóa để sử dụng ban đầu ngăn xếp cho mảng nhỏ nhưng chuyển sang một bộ đệm đống phân bổ một lần trên công suất ban đầu. (Lý do để làm nó theo cách này là không rõ ràng từ ví dụ này, nhưng cũng có khả năng làm giảm mẫu mã sưng lên, điều này là rõ ràng hơn nếu bạn xem xét thông qua các code.)
LƯU Ý: Trước khi bất cứ ai phàn nàn, điều này là không chính xác những gì họ đang làm và có thể là cách tiếp cận của họ phù hợp hơn so với những gì tôi đã đưa ra ở đây, nhưng tôi muốn hỏi về trường hợp chung.
Rõ ràng, nó hoạt động trong thực tế, nhưng tôi tò mò nếu bất cứ điều gì trong tiêu chuẩn đảm bảo cho rằng đó là trường hợp. Tôi có khuynh hướng nói không, cho N3242/expr.add:
Khi hai con trỏ đến các yếu tố của đối tượng cùng một mảng được trừ, kết quả là sự khác biệt của các chỉ số của hai phần tử mảng. ..Hơn nữa, nếu biểu thức P trỏ đến một phần tử của đối tượng mảng hoặc một phần tử cuối cùng của đối tượng mảng , và biểu thức Q trỏ tới phần tử cuối cùng của cùng một đối tượng mảng, biểu thức ((Q) +1) - (P) có cùng giá trị với ((Q) - (P)) + 1 và as - ((P) - ((Q) +1)) và có giá trị bằng 0 nếu biểu thức P điểm một phần tử cuối cùng của đối tượng mảng, mặc dù biểu thức (Q) +1 không trỏ đến một phần tử của đối tượng mảng. ... Trừ khi cả hai con trỏ trỏ đến các phần tử của cùng một đối tượng mảng hoặc một phần tử cuối cùng của đối tượng mảng, hành vi đó là không xác định.
Nhưng về mặt lý thuyết, phần giữa của đoạn trích trên, kết hợp với cách bố trí lớp học và liên kết bảo lãnh, có thể cho phép (nhỏ) điều chỉnh sau đây là hợp lệ:
#include <cassert>
#include <cstddef>
template<typename T>
struct Base
{
T initial[1];
};
template<typename T, size_t N>
struct Derived : public Base<T>
{
T rest[N - 1];
};
int main()
{
Derived<float, 10> d;
assert(&d.rest[9] - &d.rest[0] == 9);
assert(&d.rest[0] == &d.initial[1]);
assert(&d.rest[0] - &d.initial[0] == 1);
return 0;
}
mà kết hợp với các quy định khác khác nhau liên quan đến bố cục union
, khả năng chuyển đổi đến và đi từ char *
, v.v., có thể cho là mã ban đầu hợp lệ. (Vấn đề chính là thiếu độ nhạy trong định nghĩa số học con trỏ được nêu ở trên.)
Có ai biết chắc không? N3242/expr.add dường như làm rõ rằng con trỏ phải thuộc cùng một "đối tượng mảng" cho nó được xác định, nhưng có thể giả thiết là trường hợp đảm bảo khác trong tiêu chuẩn, khi được kết hợp với nhau, có thể yêu cầu một định nghĩa dù sao trong trường hợp này để duy trì một cách hợp lý tự nhất quán. (Tôi không đặt cược vào nó, nhưng tôi sẽ ít nhất là có thể tưởng tượng được.)
EDIT: @MatthieuM làm tăng phản đối rằng lớp này không phải là bố trí chuẩn và do đó có thể không được đảm bảo không chứa đệm giữa subobject cơ sở và thành viên đầu tiên của nguồn gốc, ngay cả khi cả hai đều được căn chỉnh với alignof(T)
.Tôi không chắc sự thật là thế nào, nhưng điều đó mở ra các câu hỏi biến thể sau:
Điều này có được đảm bảo để làm việc nếu di sản thừa kế đã bị xóa không?
Liệu
&d.end - &d.begin >= sizeof(float) * 10
có được đảm bảo ngay cả khi&d.end - &d.begin == sizeof(float) * 10
không?
LAST EDIT @ArneMertz lập luận cho một đọc rất chặt chẽ của N3242/expr.add (vâng, tôi biết tôi đang đọc một bản thảo, nhưng nó đủ gần), nhưng không tiêu chuẩn thực sự ngụ ý rằng sau đây có hành vi không xác định sau đó nếu đường trao đổi được loại bỏ? (Định nghĩa cùng một lớp như trên)
int main()
{
Derived<float, 10> d;
bool aligned;
float * p = &d.initial[0], * q = &d.rest[0];
++p;
if((aligned = (p == q)))
{
std::swap(p, q); // does it matter if this line is removed?
*++p = 1.0;
}
assert(!aligned || d.rest[1] == 1.0);
return 0;
}
Ngoài ra, nếu ==
là không đủ mạnh, những gì nếu chúng ta tận dụng thực tế là std::less
hình thức tổng trật tự trên con trỏ, và thay đổi các điều kiện trên để:
if((aligned = (!std::less<float *>()(p, q) && !std::less<float *>()(q, p))))
Mã có giả định rằng hai con trỏ bằng nhau trỏ đến cùng một đối tượng mảng thực sự bị hỏng theo một tiêu chuẩn đọc nghiêm ngặt không?
EDIT Xin lỗi, chỉ muốn thêm một ví dụ nữa, để loại bỏ các vấn đề bố trí tiêu chuẩn:
#include <cassert>
#include <cstddef>
#include <utility>
#include <functional>
// standard layout
struct Base
{
float initial[1];
float rest[9];
};
int main()
{
Base b;
bool aligned;
float * p = &b.initial[0], * q = &b.rest[0];
++p;
if((aligned = (p == q)))
{
std::swap(p, q); // does it matter if this line is removed?
*++p = 1.0;
q = &b.rest[1];
// std::swap(p, q); // does it matter if this line is added?
p -= 2; // is this UB?
}
assert(!aligned || b.rest[1] == 1.0);
assert(p == &b.initial[0]);
return 0;
}
Tôi không thể tin rằng có những câu hỏi hay trong thẻ C++. +1. –
Có thể là một bản sao của [Liên kết phần tử công đoàn] (http://stackoverflow.com/questions/891471/union-element-alignment), nhưng tôi không chắc chắn –
@ BЈовић câu hỏi này giả định sự hiểu biết về câu trả lời cho câu hỏi đó, thực sự –