2013-08-09 11 views
6

Mam struct jak poniżej:Jak oddać struct z 2 uint w podwójnym

struct pts_t 
{ 
    uint32_t lsb; 
    uint32_t msb; 
}; 

Chciałbym rzucić, że w podwójne. Czy to jest bezpieczne bezpośredniego napisać:

pts_t t; 

double timestamp = t; 

i bardziej skomplikowane, jeśli typ struct jest częścią DLL C API, bez konieczności „pakowania” atrybutu (dla wszystkich kompilatora) w takim przypadku muszę skopiować pts_t* otrzymujesz z wyprzedzeniem API do instancji pts_t, którą utworzę, aby sterować pakietowaniem struct?

void f(pts_t* t) 
{ 
    pts_t myt; myt.lsb = t->lsb; myt.msb = t->msb; 

    double timestamp = *(double*)(&myt.lsb); 
} 
+4

'double timestamp = (podwójnie) (* ((uint64_t *) &t));' – sgarizvi

+0

można podać przykład? –

Odpowiedz

3

Początkowa myśl byłoby napisać następujące:

double timestamp = *((double *) &(t.lsb)); 

Aby przejść przez to (zakładając, że jesteś w środowisku 32-bitowym):

  1. Otrzymujesz adres identyfikatora t.lsb, ponieważ musisz znaleźć adres pamięci pierwszego bajtu w strukturze. Zauważ, możesz alternatywnie zrobić &t.
  2. Następnie przesyłasz ten adres pamięci jako wskaźnik do podwójnego (8 bajtów).
  3. Ostatecznie wyłuskujesz ten wskaźnik i przechowujesz wszystkie 8 bajtów w 8-bajtowym bloku pamięci używanym przez identyfikator timestamp.

Uwaga:

  1. Trzeba będzie rozważyć mały/duży endianness.
  2. Zakładasz, że zarówno struktura, jak i liczba podwójna, są odpowiednio wyrównane (jak wspomniano poniżej).
  3. To nie jest przenośne, zakładasz, że podwójne to 8 bajtów.

Teraz trzy punkty w blurbie z uwagą to o co trzeba się martwić. Staje się to dużym problemem podczas przenoszenia tego kodu na wiele platform. Jak wspomniano poniżej, użycie C unions jest znacznie lepszym i poprawnym rozwiązaniem, które jest przenośne.

Byłoby to zapisać następująco z C unions:

double timestamp = (union { double d; struct pts_t pts; }) { t } .d; 
+0

'pts_t' używa' uint32_t', więc powinno działać niezależnie od wielkości standardowego 'int' (chociaż prawdopodobnie mniej prawdopodobne jest wstawienie bitów dopełniających między polami w środowisku 32-bitowym niż w środowisku 64-bitowym) – Virgile

+0

@Virgile, moje założenie o 32-bitowym środowisku dla mojej krótkiej blurb było tak, że mogę stwierdzić, że podwójne wymaga 8 bajtów. Jeśli double byłby większy lub mniejszy niż 8 bajtów, natrafiłbyś na problemy z dostępem do adresów pamięci, które nie zostały przydzielone. –

+0

Jest to niezdefiniowane zachowanie (skutkujące błędem magistrali), jeśli struktura i podwójne są wyrównane w różny sposób. Nie rób tego. –

3

Trzeba napisać

double timestamp = *((double*)(&t.lsb)); 

Należy użyć coś jak

struct __attribute__((__packed__)) pts_t { 
    ... 
}; 

aby upewnić się, że struktura jest zapakowany (choć nie widzę dlaczego każdy kompilator pad coś po lsb w tym przypadku).

...

Właściwie zależności od tego, czy platforma jest big- lub littleEndian może trzeba przełączyć LSB i MSB lub zrobić coś takiego:

double timestamp; 
double* p_timestamp = &timestamp; 
*((uint32_t*)p_timestamp) = t.msb; 
*(((uint32_t*)p_timestamp) + 1) = t.lsb; 
+0

O pakowania struct a jeśli pts_t jest przekazać jako wskaźnik przez C DLL API, zobacz moje zmienił. – alexbuisson

+0

Jest to niezdefiniowane zachowanie (skutkujące błędem magistrali), jeśli struktura i podwójne są wyrównane w różny sposób. Nie rób tego. –

+0

Ten kod również narusza zasady aliasingu. Używanie związku, jak w [odpowiedź Jensa Gustedta] (http://stackoverflow.com/a/18142604/298225) jest poprawnym sposobem ponownego interpretowania bajtów. –

4

Nawet jeśli założyć, że double są 64 bitowe, jeśli szukasz przenośnego kodu powinieneś być bardzo ostrożny z tym: strukturę i double może mieć różne ograniczenia wyrównania i kompilator może się mylić z powodu reguł aliasingu. Sposób, aby uniknąć problemów z tym jest użycie union

union { 
struct pts_t pts; 
double timestamp; 
} x = { .pts = t }; 

a następnie użyć x.timestamp.

Należy również zachować ostrożność, ponieważ "komponowanie" może spowodować dziwne wartości, takie jak nieskończoności, których nie spotkałbyś w inny sposób.

+0

@alexbuisson To jest lepsza odpowiedź – SheetJS

0

Masz:

struct pts_t 
{ 
    uint32_t lsb; 
    uint32_t msb; 
}; 

następująco:

pts_t t; 
double timestamp = t; 

jest idealnie "bezpieczne", w tym sensie, że nie będzie skompilować więc nie może wyrządzić żadnej szkody. Nie zdefiniowałeś typu pts_t; zdefiniowałeś typ struct pts_t.

to:

struct pts_t t; 
double timestamp = t; 

nie będzie również skompilować, ponieważ nie można przekonwertować (jawnie, z obsadą, lub pośrednio, z cesją) wartość typu struct do obiektu typ numeryczny.

Pokornie sugeruję, że zaoszczędziłbyś trochę czasu, gdybyś spróbował tego przed wysłaniem.

Prawdopodobnie Najprostszym sposobem jest użycie memcpy():

#include <assert.h> 
#include <string.h> 
/* ... */ 
struct pts_t t = { some_lsb_value, some_msbZ_value }; 
double timestamp; 
assert(sizeof t == sizeof timestamp); 
memcpy(&timestamp, &t, sizeof timestamp); 

Korzystając memcpy() raczej niż wskaźnik odlewy, można uniknąć jakiegokolwiek ryzyka braku wyrównania dostępu do pamięci; w zależności od systemu struktura może wymagać mniej lub bardziej ścisłego dopasowania niż double.

Nie ma również gwarancji, że struktura ma taki sam rozmiar jak double. Struktura jest prawie na pewno 64 bitów, a double jest prawdopodobnie 64 bitów, ale żadna z nich nie jest faktycznie gwarantowana i nie zaszkodzi, aby twoje założenia były jednoznaczne.

To pozostawia otwartą kwestię, czy wartości zapisane w t.lsb i t.msb, razem wzięte, stanowią reprezentację dla ważnej wartości double, szczególnie dla żądanej wartości. Język mówi bardzo niewiele o tym, jak reprezentowane są typy zmiennoprzecinkowe. W szczególności endianość może i jest różna w różnych systemach. Od Ciebie zależy, czy reinterpretacja reprezentacji w ten sposób ma sens - a Twój kod prawdopodobnie nie będzie przenośny.