2 बाइट्स को हस्ताक्षरित 16-बिट पूर्णांक में बदलने का सही तरीका क्या है?


31

में इस सवाल का जवाब , zwol इस दावे किए गए:

बाहरी स्रोत से डेटा के दो बाइट्स को 16-बिट हस्ताक्षरित पूर्णांक में बदलने का सही तरीका इस तरह सहायक कार्यों के साथ है:

#include <stdint.h>

int16_t be16_to_cpu_signed(const uint8_t data[static 2]) {
    uint32_t val = (((uint32_t)data[0]) << 8) | 
                   (((uint32_t)data[1]) << 0);
    return ((int32_t) val) - 0x10000u;
}

int16_t le16_to_cpu_signed(const uint8_t data[static 2]) {
    uint32_t val = (((uint32_t)data[0]) << 0) | 
                   (((uint32_t)data[1]) << 8);
    return ((int32_t) val) - 0x10000u;
}

उपरोक्त कार्यों में से कौन सा उपयुक्त है यह इस बात पर निर्भर करता है कि सरणी में थोड़ा एंडियन या बड़ा एंडियन प्रतिनिधित्व है या नहीं। अंत्यानुप्रास सवाल पर यहाँ नहीं है, मैं सोच रहा हूँ कि zwol में परिवर्तित मूल्य 0x10000uसे घटाव क्यों uint32_tहै int32_t

यह सही तरीका क्यों है ?

वापसी प्रकार में परिवर्तित करते समय यह कार्यान्वयन परिभाषित व्यवहार से कैसे बचता है?

चूंकि आप 2 के पूरक प्रतिनिधित्व ग्रहण कर सकते हैं, यह कैसे सरल कास्ट विफल होगा: return (uint16_t)val;

इस भोले समाधान के साथ क्या गलत है:

int16_t le16_to_cpu_signed(const uint8_t data[static 2]) {
    return (uint16_t)data[0] | ((uint16_t)data[1] << 8);
}

int16_tक्रियान्वयन-परिभाषित करते समय सटीक व्यवहार , इसलिए भोला दृष्टिकोण पोर्टेबल नहीं है।
nwellnhof

@nwellnhof में कोई कलाकार नहीं हैint16_t
एमएम

शीर्षक में प्रश्न को निर्दिष्ट करने के बिना उत्तर नहीं दिया जा सकता है कि किस मैपिंग का उपयोग करना है
एमएम

4
दोनों दृष्टिकोण कार्यान्वयन परिभाषित व्यवहार पर भरोसा करते हैं (एक हस्ताक्षरित प्रकार को एक अहस्ताक्षरित मान परिवर्तित करना जो मूल्य का प्रतिनिधित्व नहीं कर सकता है)। उदाहरण के लिए। पहले दृष्टिकोण में, के 0xFFFF0001uरूप में प्रतिनिधित्व नहीं किया जा सकता है int16_t, और दूसरे दृष्टिकोण में के 0xFFFFuरूप में प्रतिनिधित्व नहीं किया जा सकता है int16_t
Sander De Dycker

1
"चूंकि आप 2 के पूरक प्रतिनिधित्व को मान सकते हैं" [उद्धरण वांछित]। C89 और C99 ने निश्चित रूप से 1s पूरक और संकेत-परिमाण प्रतिनिधित्वों से इनकार नहीं किया। Qv, stackoverflow.com/questions/12276957/…
एरिक टावर्स

जवाबों:


20

यदि int16-बिट है तो आपका संस्करण कार्यान्वयन-परिभाषित व्यवहार पर निर्भर करता है यदि returnकथन में अभिव्यक्ति का मूल्य सीमा से बाहर है int16_t

हालाँकि पहले संस्करण में भी इसी तरह की समस्या है; उदाहरण के लिए यदि int32_tटाइप टाइप की गई है int, और इनपुट बाइट्स दोनों हैं 0xFF, तो रिटर्न स्टेटमेंट में घटाव का परिणाम होता है, UINT_MAXजो परिवर्तित होने पर कार्यान्वयन-परिभाषित व्यवहार का कारण बनता है int16_t

IMHO का उत्तर आपके पास कई प्रमुख मुद्दों से जुड़ा हुआ है।


2
लेकिन सही तरीका क्या है?
इदमीन

@ इस प्रश्न के स्पष्टीकरण की आवश्यकता है इससे पहले कि उत्तर दिया जा सकता है, मैंने प्रश्न के तहत एक टिप्पणी में अनुरोध किया है, लेकिन ओपी ने जवाब नहीं दिया है
एमएम

1
@MM: मैंने इस प्रश्न को निर्दिष्ट किया कि एंडियनस मुद्दा नहीं है। IMHO समस्या zwol को हल करने की कोशिश कर रहा है गंतव्य के प्रकार में परिवर्तित करते समय कार्यान्वयन परिभाषित व्यवहार होता है, लेकिन मैं आपसे सहमत हूं: मेरा मानना ​​है कि वह गलत है क्योंकि उसकी विधि में अन्य समस्याएं हैं। आप कार्यान्वयन परिभाषित व्यवहार को कुशलता से कैसे हल करेंगे?
चकरली

@chqrlieforyellowblockquotes मैं विशेष रूप से धीरज का जिक्र नहीं कर रहा था। क्या आप सिर्फ दो इनपुट ऑक्टेट के सटीक बिट्स को अंदर लाना int16_tचाहते हैं?
एमएम

@MM: हाँ, यह बिल्कुल सवाल है। मैंने बाइट्स लिखा लेकिन सही शब्द वास्तव में ओकटेट होना चाहिए जैसा कि टाइप है uchar8_t
चकरली

7

यह सामान्य रूप से सही होना चाहिए और उन प्लेटफार्मों पर भी काम करना चाहिए जो सामान्य 2 के पूरक के बजाय साइन बिट या 1 के पूरक अभ्यावेदन का उपयोग करते हैं । इनपुट बाइट्स को 2 के पूरक में माना जाता है।

int le16_to_cpu_signed(const uint8_t data[static 2]) {
    unsigned value = data[0] | ((unsigned)data[1] << 8);
    if (value & 0x8000)
        return -(int)(~value) - 1;
    else
        return value;
}

शाखा के कारण, यह अन्य विकल्पों की तुलना में अधिक महंगा होगा।

यह क्या पूरा करता है कि यह किसी भी धारणा से बचा जाता है कि intप्रतिनिधित्व unsignedप्लेटफॉर्म पर प्रतिनिधित्व से कैसे संबंधित है । intकिसी भी संख्या के लिए अंकगणितीय मान को संरक्षित करने के लिए कलाकारों की आवश्यकता होती है जो लक्ष्य प्रकार में फिट होंगे। क्योंकि उलटा सुनिश्चित करता है कि 16-बिट संख्या का शीर्ष शून्य होगा, मान फिट होगा। फिर 1 की एकता -और घटाव 2 के पूरक निषेध के लिए सामान्य नियम लागू करते हैं। प्लेटफ़ॉर्म पर निर्भर करता है, INT16_MINफिर भी अतिप्रवाह कर सकता है यदि यह intलक्ष्य पर प्रकार में फिट नहीं होता है , तो किस मामले longमें उपयोग किया जाना चाहिए।

प्रश्न में मूल संस्करण का अंतर रिटर्न समय पर आता है। जबकि मूल बस हमेशा घटाया जाता है 0x10000और 2 का पूरक हस्ताक्षरित अतिप्रवाह को इसे int16_tसीमा तक लपेटने देता है , इस संस्करण में स्पष्ट है ifकि हस्ताक्षरित आवरण (जो अपरिभाषित है ) से बचा जाता है ।

अब व्यवहार में, आज उपयोग किए जाने वाले लगभग सभी प्लेटफ़ॉर्म 2 के पूरक प्रतिनिधित्व का उपयोग करते हैं। वास्तव में, यदि प्लेटफ़ॉर्म में मानक-अनुपालन है stdint.hजो परिभाषित करता है int32_t, तो उसे इसके लिए 2 के पूरक का उपयोग करना होगा । जहां यह दृष्टिकोण कभी-कभी काम आता है, कुछ स्क्रिप्टिंग भाषाओं के साथ होता है, जिनमें पूर्णांक डेटा प्रकार बिल्कुल नहीं होते हैं - आप फ़्लोट के लिए ऊपर दिखाए गए संचालन को संशोधित कर सकते हैं और यह सही परिणाम देगा।


सी स्टैंडर्ड विशेष रूप से यह कहता है कि int16_tकिसी भी intxx_tऔर उनके अहस्ताक्षरित वेरिएंट को पैडिंग बिट्स के बिना 2 के पूरक प्रतिनिधित्व का उपयोग करना चाहिए। इन प्रकारों की मेजबानी करने और इसके लिए एक और प्रतिनिधित्व का उपयोग करने के लिए एक जानबूझकर विकृत वास्तुकला लगेगा int, लेकिन मुझे लगता है कि DS9K को इस तरह से कॉन्फ़िगर किया जा सकता है।
चिक्ली

@chqrlieforyellowblockquotes अच्छा बिंदु, मैं intभ्रम से बचने के लिए उपयोग करने के लिए बदल गया । दरअसल अगर मंच परिभाषित करता है int32_tतो उसे 2 का पूरक होना चाहिए।
जपा

इन प्रकारों को C99 में इस तरह से मानकीकृत किया गया था: C99 7.18.1.1 सटीक-चौड़ाई पूर्णांक प्रकार typedef नाम intN_t एक हस्ताक्षरित पूर्णांक प्रकार को चौड़ाई N, कोई पेडिंग बिट्स और दो के पूरक प्रतिनिधित्व के साथ नामित करता है। इस प्रकार, int8_tएक हस्ताक्षरित पूर्णांक प्रकार को 8 बिट्स की चौड़ाई के साथ निरूपित करता है। अन्य अभ्यावेदन अभी भी मानक द्वारा समर्थित हैं, लेकिन अन्य पूर्णांक प्रकारों के लिए।
चिक्ली

आपके अपडेट किए गए संस्करण के साथ, (int)valueयदि आपके intपास केवल 16 बिट्स हैं, तो क्रियान्वित परिभाषित व्यवहार है । मुझे डर है कि आपको उपयोग करने की आवश्यकता है (long)value - 0x10000, लेकिन गैर 2 के पूरक आर्किटेक्चर पर, मूल्य 0x8000 - 0x10000को 16-बिट के रूप में प्रस्तुत नहीं किया जा सकता है int, इसलिए समस्या बनी हुई है।
चिक्ली

@chqrlieforyellowblockquotes हाँ, बस उसी पर ध्यान दिया, मैंने इसके बजाय ~ के साथ तय किया, लेकिन longसमान रूप से अच्छी तरह से काम करेगा।
जपा

6

एक अन्य विधि - का उपयोग कर union:

union B2I16
{
   int16_t i;
   byte    b[2];
};

कार्यक्रम में:

...
B2I16 conv;

conv.b[0] = first_byte;
conv.b[1] = second_byte;
int16_t result = conv.i;

first_byteऔर second_byteकम या बड़े एंडियन मॉडल के अनुसार स्वैप किया जा सकता है। यह विधि बेहतर नहीं है, लेकिन विकल्पों में से एक है।



1
@MaximEgorushkin: विकिपीडिया सी मानक की व्याख्या के लिए एक आधिकारिक स्रोत नहीं है।
एरिक पोस्टपिसिल

2
संदेश के बजाय मैसेंजर पर ध्यान केंद्रित @EricPostpischil नासमझ है।
मैक्सिम एगोरुस्किन 13

1
@MaximEgorushkin: ओह हाँ, मैं आपकी टिप्पणी को गलत मानता हूं। मान लें byte[2]और int16_tसमान आकार हैं, यह एक या दो संभावित आदेशों में से एक है, न कि कुछ मनमाने ढंग से बिटवाइज़ मानों में फेरबदल। तो आप कम से कम संकलन समय पर पता लगा सकते हैं कि कार्यान्वयन में क्या धीरज है।
पीटर कॉर्डेस

1
मानक स्पष्ट रूप से बताता है कि संघ के सदस्य का मूल्य उस प्रकार के मूल्य प्रतिनिधित्व के रूप में सदस्य में संग्रहीत बिट्स की व्याख्या करने का परिणाम है। कार्यान्वयन-परिभाषित पहलू हैं इंफोर्रस प्रकारों का प्रतिनिधित्व कार्यान्वयन-परिभाषित है।
एमएम

6

अंकगणित संचालक शिफ्ट और बिटवाइज़-या अभिव्यक्ति की (uint16_t)data[0] | ((uint16_t)data[1] << 8)तुलना में छोटे प्रकार पर काम नहीं करते हैं int, ताकि उन uint16_tमूल्यों को बढ़ावा दिया जाए int(या unsignedयदि sizeof(uint16_t) == sizeof(int))। हालांकि, कि सही उत्तर देने चाहिए, क्योंकि केवल निचले 2 बाइट्स में मूल्य होता है।

छोटे-एंडियन रूपांतरण के लिए बड़े-एंडियन के लिए एक और उचित रूप से सही संस्करण (थोड़ा-एंडियन सीपीयू मानकर) है:

#include <string.h>
#include <stdint.h>

int16_t be16_to_cpu_signed(const uint8_t data[2]) {
    int16_t r;
    memcpy(&r, data, sizeof r);
    return __builtin_bswap16(r);
}

memcpyका प्रतिनिधित्व कॉपी करने के लिए किया जाता है int16_tऔर ऐसा करने के लिए मानक-अनुपालन तरीका है। यह संस्करण 1 निर्देश में भी संकलन करता है movbe, विधानसभा देखें ।


1
@ एमएम एक कारण __builtin_bswap16मौजूद है क्योंकि आईएसओ सी में बाइट-स्वैपिंग को कुशलता से लागू नहीं किया जा सकता है।
मैक्सिम Egorushkin

1
सच नहीं; संकलक यह पता लगा सकता है कि कोड स्वैप को कार्यान्वित करता है और इसे एक कुशल बिलिन के रूप में अनुवाद करता है
MM

1
अच्छी तरह से परिभाषित int16_tकरने के लिए परिवर्तित करना uint16_t: नकारात्मक मान मानों से अधिक INT_MAXमें परिवर्तित होते हैं , लेकिन इन मानों को वापस uint16_tपरिभाषित कार्यान्वयन व्यवहार में परिवर्तित कर दिया जाता है: 6.3.1.3 हस्ताक्षरित और अहस्ताक्षरित पूर्णांक 1. जब पूर्णांक प्रकार के साथ एक मान किसी अन्य पूर्णांक प्रकार के अलावा अन्य में परिवर्तित किया जाता है, यदि मान को नए प्रकार द्वारा दर्शाया जा सकता है, यह अपरिवर्तित है। ... 3. अन्यथा, नए प्रकार पर हस्ताक्षर किए गए हैं और इसमें मूल्य का प्रतिनिधित्व नहीं किया जा सकता है; या तो परिणाम कार्यान्वयन-परिभाषित है या कार्यान्वयन-परिभाषित संकेत उठाया जाता है।
चकरली

1
@MaximEgorushkin gcc 16-बिट संस्करण में इतना अच्छा नहीं लगता है, लेकिन क्लैगntohs / __builtin_bswapऔर |/ / <<पैटर्न के लिए समान कोड उत्पन्न करता है : gcc.godbolt.org/z/rJ-j87
PSBocik

3
@MM: मुझे लगता है कि मैक्सिम " वर्तमान संकलक के साथ व्यवहार में नहीं हो सकता" कह रहा है । बेशक एक संकलक एक बार के लिए चूसना नहीं कर सकता और एक पूर्णांक में सन्निहित बाइट्स को लोड करने को पहचान सकता है। GCC7 या 8 ने आखिरकार उन मामलों के लिए लोड / स्टोर को फिर से शुरू किया जहां बाईट-रिवर्स की जरूरत नहीं है, जीसीसी 3 ने दशकों पहले इसे गिरा दिया था। लेकिन सामान्य संकलक में बहुत सारे सामानों के साथ अभ्यास में मदद की आवश्यकता होती है जो सीपीयू कुशलतापूर्वक कर सकते हैं लेकिन आईएसओ सी ने उपेक्षित / अस्वीकार करने के लिए आंशिक रूप से उजागर नहीं किया है। पोर्टेबल आईएसओ सी कुशल कोड बिट / बाइट-हेरफेर के लिए एक अच्छी भाषा नहीं है।
पीटर कॉर्डेस

4

यहां एक और संस्करण है जो केवल पोर्टेबल और अच्छी तरह से परिभाषित व्यवहारों पर निर्भर करता है (हेडर #include <endian.h>मानक नहीं है, कोड है):

#include <endian.h>
#include <stdint.h>
#include <string.h>

static inline void swap(uint8_t* a, uint8_t* b) {
    uint8_t t = *a;
    *a = *b;
    *b = t;
}
static inline void reverse(uint8_t* data, int data_len) {
    for(int i = 0, j = data_len / 2; i < j; ++i)
        swap(data + i, data + data_len - 1 - i);
}

int16_t be16_to_cpu_signed(const uint8_t data[2]) {
    int16_t r;
#if __BYTE_ORDER == __LITTLE_ENDIAN
    uint8_t data2[sizeof r];
    memcpy(data2, data, sizeof data2);
    reverse(data2, sizeof data2);
    memcpy(&r, data2, sizeof r);
#else
    memcpy(&r, data, sizeof r);
#endif
    return r;
}

थोड़ा-एंडियन संस्करण एकल movbeनिर्देश के साथ संकलित करता है clang, gccसंस्करण कम इष्टतम है, विधानसभा देखें ।


@chqrlieforyellowblockquotes आपका मुख्य चिंता का विषय रहा है लगता है uint16_tके लिए int16_tरूपांतरण, इस संस्करण तुम जाओ तो यहाँ है कि रूपांतरण नहीं है,।
मैक्सिम Egorushkin

2

मैं उनके जवाब के लिए सभी योगदानकर्ताओं को धन्यवाद देना चाहता हूं। यहाँ सामूहिक कार्य निम्नलिखित हैं:

  1. C मानक 7.20.1.1 के अनुसार सटीक-चौड़ाई पूर्णांक प्रकार : प्रकार uint8_t, int16_tऔर uint16_tकिसी भी पैडिंग बिट्स के बिना दो के पूरक प्रतिनिधित्व का उपयोग करना चाहिए, इसलिए प्रतिनिधित्व के वास्तविक बिट्स स्पष्ट रूप से सरणी में 2 बाइट्स के द्वारा निर्दिष्ट क्रम में हैं। फ़ंक्शन के नाम।
  2. अहस्ताक्षरित 16 बिट मान (unsigned)data[0] | ((unsigned)data[1] << 8)(थोड़ा एंडियन संस्करण के लिए) एक निर्देश के लिए संकलित करता है और एक अहस्ताक्षरित 16-बिट मान प्राप्त करता है।
  3. सी मानक के अनुसार 6.3.1.3 हस्ताक्षरित और अहस्ताक्षरित पूर्णांक : uint16_tहस्ताक्षरित प्रकार के मान को परिवर्तित करने पर int16_tकार्यान्वयन परिभाषित व्यवहार होता है यदि मूल्य गंतव्य प्रकार की सीमा में नहीं है। उन प्रकारों के लिए कोई विशेष प्रावधान नहीं किया गया है जिनका प्रतिनिधित्व ठीक परिभाषित है।
  4. इस क्रियान्वित परिभाषित व्यवहार से बचने के लिए, कोई परीक्षण कर सकता है यदि अहस्ताक्षरित मान इससे बड़ा है INT_MAXऔर घटाकर संबंधित हस्ताक्षरित मूल्य की गणना करें 0x10000Zwol द्वारा सुझाए गए सभी मूल्यों के लिए ऐसा करने int16_tसे एक ही कार्यान्वयन परिभाषित व्यवहार के साथ सीमा के बाहर मूल्य उत्पन्न हो सकते हैं ।
  5. 0x8000बिट के लिए परीक्षण स्पष्ट रूप से अक्षम कोड का उत्पादन करने का कारण बनता है।
  6. एक कार्यान्वयन परिभाषित व्यवहार का उपयोग करता है के बिना अधिक कुशल रूपांतरण punning प्रकार एक संघ के माध्यम से, लेकिन इस दृष्टिकोण का definedness के बारे में बहस भी सी स्टैंडर्ड समिति स्तर पर अभी भी खुला है,।
  7. टाइप पाइंटिंग को पोर्ट्रेट तरीके से और परिभाषित व्यवहार के साथ प्रयोग किया जा सकता है memcpy

अंक 2 और 7 को मिलाकर, यहां एक पोर्टेबल और पूरी तरह से परिभाषित समाधान है जो कि जीसीसी और क्लैंग दोनों के साथ एकल निर्देश के लिए कुशलतापूर्वक संकलित करता है :

#include <stdint.h>
#include <string.h>

int16_t be16_to_cpu_signed(const uint8_t data[2]) {
    int16_t r;
    uint16_t u = (unsigned)data[1] | ((unsigned)data[0] << 8);
    memcpy(&r, &u, sizeof r);
    return r;
}

int16_t le16_to_cpu_signed(const uint8_t data[2]) {
    int16_t r;
    uint16_t u = (unsigned)data[0] | ((unsigned)data[1] << 8);
    memcpy(&r, &u, sizeof r);
    return r;
}

64-बिट विधानसभा :

be16_to_cpu_signed(unsigned char const*):
        movbe   ax, WORD PTR [rdi]
        ret
le16_to_cpu_signed(unsigned char const*):
        movzx   eax, WORD PTR [rdi]
        ret

मैं एक भाषा वकील नहीं हूं, लेकिन केवल charअन्य प्रकार से किसी अन्य प्रकार के ऑब्जेक्ट प्रतिनिधित्व को उपनाम या शामिल कर सकते हैं। uint16_tनहीं में से एक है char, ताकि प्रकार, memcpyके uint16_tलिए int16_tअच्छी तरह से परिभाषित व्यवहार नहीं है। मानक को केवल अच्छी तरह से परिभाषित करने के लिए char[sizeof(T)] -> T > char[sizeof(T)]रूपांतरण की आवश्यकता होती memcpyहै।
मैक्सिम एगोरुस्किन

memcpyको लागू uint16_tकरने के लिए int16_tसबसे अच्छा, पोर्टेबल नहीं, अच्छी तरह से परिभाषित नहीं, बिल्कुल एक से दूसरे के काम के रूप में परिभाषित किया गया है, और आप जादुई रूप से उस के साथ चक्कर नहीं लगा सकते memcpy। इससे कोई फर्क नहीं पड़ता कि uint16_tदो के पूरक प्रतिनिधित्व का उपयोग करता है या नहीं, या पैडिंग बिट्स मौजूद हैं या नहीं - यह सी मानक द्वारा परिभाषित या आवश्यक व्यवहार नहीं है।
मैक्सिम एगोरुस्किन

इतने सारे शब्दों के साथ, आपके "समाधान" को बदलने के r = uलिए उबलता है, memcpy(&r, &u, sizeof u)लेकिन बाद वाला पूर्व की तुलना में बेहतर नहीं है, है ना?
मैक्सिम इगोरुस्किन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.