كتابة وقراءة القيمة الطويلة الدولية في كود C

StackOverflow https://stackoverflow.com/questions/1105938

  •  12-09-2019
  •  | 
  •  

سؤال

أنا أعمل على تنسيق ملف يجب كتابة وقراءة في العديد من أنظمة التشغيل والأجهزة الكمبيوتر المختلفة. يجب أن تكون بعض أجهزة الكمبيوتر هذه آلات X86، والآخرين X86-64. قد توجد بعض المعالجات الأخرى، لكنني لست قلقا بشأنها بعد.

يجب أن يحتوي تنسيق الملفات هذا على عدة أرقام سيتم قراءتها مثل هذا:

struct LongAsChars{
    char c1, c2, c3, c4;
};

long readLong(FILE* file){
    int b1 = fgetc(file);
    int b2 = fgetc(file);
    int b3 = fgetc(file);
    int b4 = fgetc(file);
    if(b1<0||b2<0||b3<0||b4<0){
        //throwError
    }

    LongAsChars lng;
    lng.c1 = (char) b1;
    lng.c2 = (char) b2;
    lng.c3 = (char) b3;
    lng.c4 = (char) b4;

    long* value = (long*) &lng;

    return *value;
}

وكتبت كما:

void writeLong(long x, FILE* f){
    long* xptr = &x;
    LongAsChars* lng = (LongAsChars*) xptr;
    fputc(lng->c1, f);
    fputc(lng->c2, f);
    fputc(lng->c3, f);
    fputc(lng->c4, f);
}

على الرغم من أن هذا يبدو يعمل على جهاز الكمبيوتر الخاص بي، فأنا قلق من أنه قد لا يجوز للآخرين أو أن تنسيق الملف قد ينتهي بمختلف عبر أجهزة الكمبيوتر (32 بت VS 64 Bits Computers، على سبيل المثال). أفعل شيئا خاطئا؟ كيف يمكنني تطبيق التعليمات البرمجية الخاصة بي لاستخدام عدد ثابت من البايتات لكل رقم؟

يجب أن أستخدم فقط FREED (والتي ربما تجعل التعليمات البرمجية أسرع أيضا) بدلا من ذلك؟

هل كانت مفيدة؟

المحلول

استخدام الأنواع في stdint.h لضمان حصولك على نفس العدد من البايتات والخروج.

ثم تركت فقط مع التعامل مع مشكلات النبيذ، والتي ربما لا تتعامل بها الكود حقا.

تسلسلك الطويل مع سحر المستعثية * يتركك مع أوامر بايت مختلفة في الملف المكتوب للمنصات مع ننذة مختلفة.

يجب أن تتحلل البايتات شيئا مثل ذلك:

char c1 = (val >>  0) & 0xff;
char c2 = (val >>  8) & 0xff;
char c3 = (val >> 16) & 0xff;
char c4 = (val >> 24) & 0xff;

وتصلح ثم استخدام شيء مثل:

val = (c4 << 24) |
      (c3 << 16) |
      (c2 <<  8) |
      (c1 <<  0);

نصائح أخرى

قد تواجه أيضا إلى مشاكل مع الانتخابات. وبعد لماذا لا تستخدم شيئا مثل netcdf. أو HDF., ، التي تعتني بأي قضايا قابلية للسرعة التي قد تنشأ؟

بدلا من استخدام الهياكل مع الشخصيات فيها، فكر في نهج أكثر رياضيا:

long l  = fgetc() << 24;
     l |= fgetc() << 16;
     l |= fgetc() <<  8;
     l |= fgetc() <<  0;

هذا أكثر مباشرة وواضحة حول ما تحاول إنجازه. يمكن أيضا تنفيذها في حلقة للتعامل مع الأرقام الكبيرة.

أنت لا تريد استخدام int طويل. يمكن أن تكون أحجام مختلفة على منصات مختلفة، وبالتالي فهي غير مبتدئ لتنسيق مستقل من النظام الأساسي. عليك أن تقرر أي مدى من القيم التي يجب تخزينها في الملف. 32 بت من المحتمل أن تكون أسهل.

أنت تقول أنك لا تشعر بالقلق بشأن المنصات الأخرى بعد. وبعد سآخذ ذلك يعني أنك تريد الاحتفاظ بإمكانية دعمها، وفي هذه الحالة يجب عليك تحديد ترتيب البايت بتنسيق الملف الخاص بك. X86 هو نبيذ صغير، لذلك قد تعتقد أن هذا هو الأفضل. لكن Big-Endian هو طلب التبادل "القياسي" إذا كان أي شيء، لأنه يستخدم في الشبكات.

إذا ذهبت ل Big-Endian ("Network Byte Order"):

// can't be bothered to support really crazy platforms: it is in
// any case difficult even to exchange files with 9-bit machines,
// so we'll cross that bridge if we come to it.
assert(CHAR_BIT == 8);
assert(sizeof(uint32_t) == 4);

{
    // write value
    uint32_t value = 23;
    const uint32_t networkOrderValue = htonl(value);
    fwrite(&networkOrderValue, sizeof(uint32_t), 1, file);
}

{
    // read value
    uint32_t networkOrderValue;
    fread(&networkOrderValue, sizeof(uint32_t), 1, file);
    uint32_t value = ntohl(networkOrderValue);
}

في الواقع، لا تحتاج حتى إلى إعلان اثنين من المتغيرات، إنه أمر مربكي بعض الشيء لاستبدال "القيمة" مع ما يعادل ترتيب الشبكة في نفس المتغير.

إنه يعمل لأن "ترتيب البايت الشبكي" محددة ليكون مهما كان ترتيب البتات في ترتيب قابل للتبديل (إخراج كبير) في الذاكرة. لا حاجة للفوضى بالنقابات لأن أي كائن مخزن في C يمكن أن يعامل كسلسلة من char. لا حاجة إلى حالة خاصة للإختناع لأن هذا ما ntohl / htonl من أجله.

إذا كان هذا بطيئا للغاية، فيمكنك البدء في التفكير في تبديل البايت المحسن من النظام الأساسي بشكل واضح، مع SIMD أو Other. أو باستخدام Little-Endian، على افتراض أن معظم منصاتك ستكون إيندايا قليلة، فمن الأسرع "في المتوسط" عبرها. في هذه الحالة، ستحتاج إلى الكتابة أو العثور على "المضيف إلى وظائف" Little-Endian "و" Little-Endian لاستضافة "، والتي بالطبع على X86 فقط لا تفعل شيئا.

أعتقد أن نهج العمارة الأكثر تعبرا هو استخدام أنواع UINTXX_T، كما هو محدد في Stdint.h. انظر صفحة الرجل هنا. على سبيل المثال، ستمنحك INT32_T عددا صحيحا 32 بت على X86 و X86-64. يمكنني استخدام هذه بشكل افتراضي الآن في جميع التعليمات البرمجية الخاصة بي ولم يكن لديك أي مشاكل، لأنها معيار إلى حد ما عبر كل شيء * Nix.

افترض sizeof(uint32_t) == 4, ، هناك 4!=24 أوامر البايت الممكنة، والتي تعد الرؤية الصغيرة والهنداء الصغار هي الأمثلة البارزة، ولكن تم استخدام الآخرين أيضا (مثل PDP-Endian).

فيما يلي وظائف لقراءة وكتابة أعداد صحيحة غير موقعة 32 بت من دفق، واستعادة ترتيب بايت تعسفي يتم تحديده من قبل عدد صحيح يمثل تمثيله تسلسل البايت 0,1,2,3: endian.h., endian.c.

يعرف الرأس هذه النماذج

_Bool read_uint32(uint32_t * value, FILE * file, uint32_t order);
_Bool write_uint32(uint32_t value, FILE * file, uint32_t order);

وهذه الثوابت

LITTLE_ENDIAN
BIG_ENDIAN
PDP_ENDIAN
HOST_ORDER
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top