std :: bit_cast với std :: mảng


14

Trong cuộc nói chuyện gần đây của anh ấy, kiểu lén lút trong C ++ hiện đại Timur Doumler nói rằng std::bit_castkhông thể sử dụng bit để floatchuyển thành một unsigned char[4]vì các mảng kiểu C không thể được trả về từ một hàm. Chúng ta nên sử dụng std::memcpyhoặc đợi cho đến C ++ 23 (hoặc mới hơn) khi một cái gì đó giống như reinterpret_cast<unsigned char*>(&f)[i]sẽ được xác định rõ.

Trong C ++ 20, chúng ta có thể sử dụng std::arrayvới std::bit_cast,

float f = /* some value */;
auto bits = std::bit_cast<std::array<unsigned char, sizeof(float)>>(f);

thay vì mảng kiểu C để lấy byte của a float?

Câu trả lời:


15

Vâng, điều này hoạt động trên tất cả các trình biên dịch chính, và theo như tôi có thể thấy khi nhìn vào tiêu chuẩn, nó có thể mang theo và đảm bảo hoạt động.

Trước hết, std::array<unsigned char, sizeof(float)>được đảm bảo là một tổng hợp ( https://eel.is/c++draft/array#overview-2 ). Từ đó, nó chứa chính xác một sizeof(float)số chars bên trong (thường là a char[], mặc dù tiêu chuẩn không bắt buộc thực hiện cụ thể này - nhưng nó nói rằng các yếu tố phải liền kề nhau) và không thể có thêm bất kỳ thành viên không tĩnh nào.

Do đó, nó có thể sao chép một cách tầm thường, và kích thước của nó cũng phù hợp với float.

Hai thuộc tính cho phép bạn ở bit_castgiữa chúng.


3
Lưu ý rằng struct X { unsigned char elems[5]; };đáp ứng quy tắc bạn trích dẫn. Nó chắc chắn có thể được khởi tạo danh sách với tối đa 4 yếu tố. Nó cũng có thể được khởi tạo danh sách với 5 yếu tố. Tôi không nghĩ rằng bất kỳ người triển khai thư viện tiêu chuẩn nào cũng ghét mọi người thực sự làm điều này, nhưng tôi nghĩ rằng nó phù hợp về mặt kỹ thuật.
Barry

Cảm ơn! - Barry, tôi không nghĩ điều đó hoàn toàn đúng. Tiêu chuẩn cho biết: "có thể được khởi tạo danh sách với tối đa N phần tử". Giải thích của tôi là "lên đến" ngụ ý "không hơn". Điều đó có nghĩa là bạn không thể làm elems[5]. Và tại thời điểm đó tôi không thể thấy làm thế nào bạn có thể kết thúc với một tổng hợp ở đâu sizeof(array<char, sizeof(T)>) != sizeof(T)?
Timur Doumler

Tôi tin rằng mục đích của quy tắc ("một tổng hợp có thể được khởi tạo danh sách ...") là để cho phép struct X { unsigned char c1, c2, c3, c4; };hoặc struct X { unsigned char elems[4]; };- vì vậy trong khi các ký tự cần phải là các phần tử của tổng hợp đó, điều này cho phép chúng là các phần tử tổng hợp trực tiếp hoặc các yếu tố của một tổng hợp phụ.
Timur Doumler

2
@Timur "lên đến" không ngụ ý "không nhiều hơn". Theo cùng một cách mà hàm ý P -> Qkhông ngụ ý bất cứ điều gì về trường hợp!P
Barry

1
Ngay cả khi tổng hợp không chứa gì ngoài một mảng gồm 4 phần tử chính xác, không có gì đảm bảo rằng arraychính nó sẽ không có phần đệm. Việc triển khai nó có thể không có phần đệm (và bất kỳ triển khai nào nên được coi là rối loạn chức năng), nhưng không có gì đảm bảo rằng arraybản thân nó sẽ không.
Nicol Bolas

6

Câu trả lời được chấp nhận là không chính xác vì nó không xem xét các vấn đề liên kết và đệm.

Mỗi [mảng] / 1-3 :

Tiêu đề <array>xác định một mẫu lớp để lưu trữ các chuỗi đối tượng có kích thước cố định. Một mảng là một container liền kề. Một thể hiện của array<T, N>các Nphần tử lưu trữ của loại T, vì vậy đó size() == Nlà một bất biến.

Mảng là một tập hợp có thể được khởi tạo danh sách với tối đa N các phần tử có kiểu có thể chuyển đổi thành T.

Một mảng đáp ứng tất cả các yêu cầu của một container và của một container có thể đảo ngược ( [container.requirements]), ngoại trừ một đối tượng mảng được xây dựng mặc định không trống và hoán đổi đó không có độ phức tạp không đổi. Một mảng đáp ứng một số yêu cầu của một thùng chứa trình tự. Mô tả được cung cấp ở đây chỉ cho các hoạt động trên mảng không được mô tả trong một trong các bảng này và cho các hoạt động có thêm thông tin ngữ nghĩa.

Tiêu chuẩn thực sự không yêu cầu std::arrayphải có chính xác một thành viên dữ liệu công khai loại T[N], vì vậy về mặt lý thuyết có thể là như vậy sizeof(To) != sizeof(From)hoặc is_­trivially_­copyable_­v<To>.

Tôi sẽ ngạc nhiên nếu điều này không hoạt động trong thực tế, mặc dù.


2

Đúng.

Theo bài viết mô tả hành vi của std::bit_castvà việc thực hiện đề xuất của nó cho đến khi cả hai loại có cùng kích thước và có thể sao chép một cách tầm thường, diễn viên sẽ thành công.

Việc thực hiện đơn giản hóa std::bit_castphải giống như:

template <class Dest, class Source>
inline Dest bit_cast(Source const &source) {
    static_assert(sizeof(Dest) == sizeof(Source));
    static_assert(std::is_trivially_copyable<Dest>::value);
    static_assert(std::is_trivially_copyable<Source>::value);

    Dest dest;
    std::memcpy(&dest, &source, sizeof(dest));
    return dest;
}

Vì một float (4 byte) và một mảng đối unsigned charvới size_of(float)tất cả các xác nhận đó, nên phần bên dưới std::memcpysẽ được thực hiện. Do đó, mỗi phần tử trong mảng kết quả sẽ là một byte liên tiếp của float.

Để chứng minh hành vi này, tôi đã viết một ví dụ nhỏ trong Compiler Explorer mà bạn có thể thử tại đây: https://godbolt.org/z/4G21zS . Phao 5.0 được lưu trữ chính xác dưới dạng một mảng byte ( Ox40a00000) tương ứng với biểu diễn thập lục phân của số float đó trong Big Endian .


Bạn có chắc chắn rằng nó std::arrayđược đảm bảo không có bit đệm vv?
LF

1
Thật không may, thực tế là một số mã hoạt động không ngụ ý không có UB trong đó. Ví dụ, chúng ta có thể viết auto bits = reinterpret_cast<std::array<unsigned char, sizeof(float)>&>(f)và nhận chính xác cùng một đầu ra. Nó có chứng minh điều gì không?
Evg

@LF theo đặc điểm kỹ thuật: std::arrayđáp ứng các yêu cầu của ContiguiosContainer (kể từ C ++ 17) .
Manuel Gil

1
@ManuelGil: std::vectorcũng thỏa mãn các tiêu chí tương tự và rõ ràng không thể được sử dụng ở đây. Có một cái gì đó yêu cầu std::arraygiữ các phần tử bên trong lớp (trong một trường), ngăn nó trở thành một con trỏ đơn giản đến mảng bên trong không? (như trong vectơ, cũng có kích thước, mảng nào không bắt buộc phải có trong một trường)
firda

@firda Yêu cầu tổng hợp của std::arrayhiệu quả đòi hỏi nó phải lưu trữ các yếu tố bên trong, nhưng tôi lo lắng về các vấn đề bố trí.
LF
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.