حل ل "dereferencing` void *'pointer "تحذير في الهيكل في c؟

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

  •  08-07-2019
  •  | 
  •  

سؤال

كنت أحاول إنشاء بنية زائفة سوبر لطباعة مجموعة من الهياكل. هياكلي الأساسية هي كما يلي.

/* Type 10 Count */
typedef struct _T10CNT
{
    int _cnt[20];
} T10CNT;

...

/* Type 20 Count */
typedef struct _T20CNT
{
    long _cnt[20];
} T20CNT;
...

لقد قمت بإنشاء البنية أدناه لطباعة مجموعة الهياكل المذكورة أعلاه. لقد حصلت على خطأ مؤشر Void Dereferencing أثناء تجميع مقتطف الكود أدناه.

typedef struct _CMNCNT
{
    long  _cnt[3];
} CMNCNT;

static int printCommonStatistics(void *cmncntin, int cmncnt_nelem, int cmncnt_elmsize)
{
    int ii;
    for(ii=0; ii<cmncnt_nelem; ii++)
    {
        CMNCNT *cmncnt = (CMNCNT *)&cmncntin[ii*cmncnt_elmsize];
        fprintf(stout,"STATISTICS_INP: %d\n",cmncnt->_cnt[0]);
        fprintf(stout,"STATISTICS_OUT: %d\n",cmncnt->_cnt[1]); 
        fprintf(stout,"STATISTICS_ERR: %d\n",cmncnt->_cnt[2]);
    }
    return SUCCESS;
}

T10CNT struct_array[10];
...
printCommonStatistics(struct_array, NELEM(struct_array), sizeof(struct_array[0]);
...

نيتي هو أن يكون لدي وظيفة مشتركة لطباعة جميع المصفوفات. واسمحوا لي أن أعرف الطريقة الصحيحة لاستخدامه.

نقدر المساعدة مقدمًا.

تحرير: تم تغيير اسم المعلمة إلى cmncntin من CMNCNT. آسف لقد كان خطأ مطبعي.

شكرا ، ماثيو ليجو

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

المحلول

أعتقد أن تصميمك سوف يفشل ، لكنني أيضًا غير مقتنع بأن الإجابات الأخرى التي أراها تتعامل تمامًا مع الأسباب الأعمق.

يبدو أنك تحاول استخدام C للتعامل مع الأنواع العامة ، وهو أمر يصبح دائمًا شعرًا. يمكنك القيام بذلك ، إذا كنت حريصًا ، لكن الأمر ليس بالأمر السهل ، وفي هذه الحالة ، أشك في أنه سيكون جديراً بالاهتمام.

سبب أعمق: لنفترض أننا نتجاوز مجرد مشاكل النحوية (أو بالكاد أكثر من النحوية). يوضح الرمز الخاص بك أن T10CNT يحتوي على 20 int و T20CNT يحتوي على 20 long. على آلات 64 بت الحديثة - بخلاف Win64 - sizeof(long) != sizeof(int). لذلك ، يجب أن يكون الرمز الموجود داخل وظيفة الطباعة الخاصة بك يميز بين إزالة الإخلاء int المصفوفات و long المصفوفات. في C ++ ، هناك قاعدة يجب ألا تحاول التعامل مع المصفوفات متعددة الأشكال ، وهذا النوع من الأشياء هو السبب. يحتوي نوع CMNCNT على 3 long القيم؛ يختلف عن كل من هياكل T10CNT و T20CNT في العدد ، على الرغم من أن النوع الأساسي من المصفوفة يتطابق مع T20CNT.

توصية نمط: أوصي بشدة بتجنب الرائدة السفلية على الأسماء. بشكل عام ، يتم حجز الأسماء التي تبدأ من السطح السفلي للتنفيذ لاستخدامها ، واستخدام وحدات الماكرو. وحدات الماكرو ليس لها احترام للنطاق. إذا حدد التطبيق الماكرو _cnt ، فسوف يدمر الكود الخاص بك. هناك فروق دقيقة لما تم حجز الأسماء ؛ أنا لست على وشك الذهاب إلى تلك الفروق الدقيقة. من الأسهل بكثير التفكير في أن "أسماء بدءًا من السطح السفلي محجوز" ، وسوف يوجهك إلى المتاعب.

اقتراح أسلوب: تُرجع وظيفة الطباعة النجاح دون قيد أو شرط. هذا غير معقول. يجب أن تعيد وظيفتك شيئًا ، بحيث لا يتعين على المتصل اختبار النجاح أو الفشل (لأنه لا يمكن أن يفشل أبدًا). المبرمج الدقيق الذي يلاحظ أن الوظيفة إرجاع الحالة ستختبر دائمًا حالة الإرجاع ، ولديها رمز معالجة الأخطاء. لن يتم تنفيذ هذا الرمز أبدًا ، لذا فهو ميت ، لكن من الصعب على أي شخص (أو المترجم) تحديد ذلك.

إصلاح السطح: مؤقتًا ، يمكننا أن نفترض أنه يمكنك علاج int و long كما المرادفات ولكن يجب أن تخرج من عادة التفكير بأنها مرادفات. ال void * الوسيطة هي الطريقة الصحيحة لقول "هذه الوظيفة تأخذ مؤشرًا من النوع غير المحدد". ومع ذلك ، داخل الوظيفة ، تحتاج إلى التحويل من أ void * إلى نوع معين قبل القيام بفهرسة.

typedef struct _CMNCNT
{
    long    count[3];
} CMNCNT;

static void printCommonStatistics(const void *data, size_t nelem, size_t elemsize)
{
    int i;
    for (i = 0; i < nelem; i++)
    {
        const CMNCNT *cmncnt = (const CMNCNT *)((const char *)data + (i * elemsize));
        fprintf(stdout,"STATISTICS_INP: %ld\n", cmncnt->count[0]);
        fprintf(stdout,"STATISTICS_OUT: %ld\n", cmncnt->count[1]); 
        fprintf(stdout,"STATISTICS_ERR: %ld\n", cmncnt->count[2]);
    }
}

(أحب فكرة دفق الملفات المسمى stout جدا. اقتراح: استخدام Cut'n'paste على رمز المصدر الحقيقي-إنه أكثر أمانًا! أنا أستخدم عمومًا "sed 's/^/ /' file.c"لإعداد رمز ل cut'n'paste في الإجابة SO.)

ماذا يفعل هذا الخط المصبوب؟ أنا سعيد لأنك سألت ...

  • العملية الأولى هي تحويل const void * الى const char *; ؛ يتيح لك ذلك القيام بعمليات بحجم البايت على العنوان. في الأيام التي سبقت قياسي C ، char * تم استخدامه بدلاً من void * كما آلية العنوان العالمية.
  • تضيف العملية التالية العدد الصحيح للبايت للوصول إلى بداية iالعنصر التابع لمجموعة كائنات الحجم elemsize.
  • ثم يخبر فريق العمل الثاني المترجم "ثق بي - أعرف ما أفعله" و "تعامل مع هذا العنوان كعنوان لهيكل CMNCNT".

من هناك ، الرمز سهل بما فيه الكفاية. لاحظ أنه منذ أن يحتوي بنية CMNCNT long القيمة ، استخدمت %ld أن أقول الحقيقة fprintf().

نظرًا لأنك لست على وشك تعديل البيانات في هذه الوظيفة ، فهي ليست فكرة سيئة لاستخدام const التصفيات كما فعلت.

لاحظ أنه إذا كنت ستكون مخلصًا sizeof(long) != sizeof(int), ، ثم تحتاج إلى كتلتين منفصلتين من التعليمات البرمجية (أقترح وظائف منفصلة) للتعامل مع صفيف " int"و" صفيف من long"أنواع الهيكل.

نصائح أخرى

يتم ترك نوع الفراغ غير مكتمل عمدا. من هذا ، يتبع أنه لا يمكنك dereference مؤشرات باطلة ، ولا يمكنك أن تأخذ حجمها. هذا يعني أنه لا يمكنك استخدام مشغل Subcript باستخدامه مثل صفيف.

في اللحظة التي تقوم فيها بتعيين شيء إلى مؤشر باطل ، تُفقد أي معلومات من النوع الذي تم إبرامه الأصلي ، بحيث لا يمكنك إلا أن ترسخ إذا قمت بإعادته لأول مرة إلى نوع المؤشر الأصلي.

أولا والأهم ، تمر T10CNT* إلى الوظيفة ، لكنك تحاول أن تكتب (و dereference) ذلك CMNCNT* في وظيفتك. هذا غير صالح وغير محدد.

تحتاج إلى دالة PrintCommonStatistics لكل نوع من عناصر الصفيف. لذلك ، لديك أprintCommonStatisticsInt, printCommonStatisticsLong, printCommonStatisticsChar الذي يختلف كل شيء بحجة الأولى (يأخذ واحد int*, والآخر يأخذ long*, ، وهلم جرا). يمكنك إنشاءها باستخدام وحدات الماكرو ، لتجنب الكود الزائد.

إن اجتياز البنية نفسه ليس فكرة جيدة ، ومنذ ذلك الحين ، عليك تحديد وظيفة جديدة لكل حجم مختلف من الصفيف المحتوي داخل البنية (نظرًا لأنها جميع أنواع مختلفة). لذا من الأفضل تمرير المصفوفة الموجودة مباشرة (struct_array[0]._cnt, ، اتصل بالوظيفة لكل فهرس)

قم بتغيير إعلان الوظيفة إلى char * مثل ذلك:

static int printCommonStatistics(char *cmncnt, int cmncnt_nelem, int cmncnt_elmsize)

لا يفترض نوع الفراغ أي حجم معين في حين أن char سوف يفترض حجم البايت.

You can't do this:

cmncnt->_cnt[0]

if cmnct is a void pointer.

You have to specify the type. You may need to re-think your implementation.

The function

static int printCommonStatistics(void *cmncntin, int cmncnt_nelem, int cmncnt_elmsize)
{
    char *cmncntinBytes;
    int ii;

    cmncntinBytes = (char *) cmncntin;
    for(ii=0; ii<cmncnt_nelem; ii++)
    {
        CMNCNT *cmncnt = (CMNCNT *)(cmncntinBytes + ii*cmncnt_elmsize);  /* Ptr Line */
        fprintf(stdout,"STATISTICS_INP: %d\n",cmncnt->_cnt[0]);
        fprintf(stdout,"STATISTICS_OUT: %d\n",cmncnt->_cnt[1]); 
        fprintf(stdout,"STATISTICS_ERR: %d\n",cmncnt->_cnt[2]);
    }
    return SUCCESS;
}

Works for me.

The issue is that on the line commented "Ptr Line" the code adds a pointer to an integer. Since our pointer is a char * we move forward in memory sizeof(char) * ii * cmncnt_elemsize, which is what we want since a char is one byte. Your code tried to do an equivalent thing moving forward sizeof(void) * ii * cmncnt_elemsize, but void doesn't have a size, so the compiler gave you the error.

I'd change T10CNT and T20CNT to both use int or long instead of one with each. You're depending on sizeof(int) == sizeof(long)

On this line:

CMNCNT *cmncnt = (CMNCNT *)&cmncnt[ii*cmncnt_elmsize];

You are trying to declare a new variable called cmncnt, but a variable with this name already exists as a parameter to the function. You might want to use a different variable name to solve this.

Also you may want to pass a pointer to a CMNCNT to the function instead of a void pointer, because then the compiler will do the pointer arithmetic for you and you don't have to cast it. I don't see the point of passing a void pointer when all you do with it is cast it to a CMNCNT. (Which is not a very descriptive name for a data type, by the way.)

Your expression

(CMNCNT *)&cmncntin[ii*cmncnt_elmsize]

tries to take the address of cmncntin[ii*cmncnt_elmsize] and then cast that pointer to type (CMNCNT *). It can't get the address of cmncntin[ii*cmncnt_elmsize] because cmncntin has type void*.

Study C's operator precedences and insert parentheses where necessary.

Point of Information: Internal Padding can really screw this up.

Consider struct { char c[6]; }; -- It has sizeof()=6. But if you had an array of these, each element might be padded out to an 8 byte alignment!

Certain assembly operations don't handle mis-aligned data gracefully. (For example, if an int spans two memory words.) (YES, I have been bitten by this before.)

.

Second: In the past, I've used variably sized arrays. (I was dumb back then...) It works if you are not changing type. (Or if you have a union of the types.)

E.g.:

struct T { int sizeOfArray;  int data[1]; };

Allocated as

T * t = (T *) malloc( sizeof(T) + sizeof(int)*(NUMBER-1) );
                      t->sizeOfArray = NUMBER;

(Though padding/alignment can still screw you up.)

.

Third: Consider:

   struct T {
     int sizeOfArray;
     enum FOO arrayType;
     union U { short s; int i; long l; float f; double d; } data [1];
    };

It solves problems with knowing how to print out the data.

.

Fourth: You could just pass in the int/long array to your function rather than the structure. E.g:

void printCommonStatistics( int * data, int count )
{
  for( int i=0;  i<count;  i++ )
    cout << "FOO: " << data[i] << endl;
}

Invoked via:

_T10CNT  foo;
printCommonStatistics( foo._cnt, 20 );

Or:

 int a[10], b[20], c[30];
printCommonStatistics( a, 10 );
printCommonStatistics( b, 20 );
printCommonStatistics( c, 30 );

This works much better than hiding data in structs. As you add members to one of your struct's, the layout may change between your struct's and no longer be consistent. (Meaning the address of _cnt relative to the start of the struct may change for _T10CNT and not for _T20CNT. Fun debugging times there. A single struct with a union'ed _cnt payload would avoid this.)

E.g.:

struct FOO {
  union {
         int     bar  [10];
          long biff [20];
   } u;
}

.

Fifth: If you must use structs... C++, iostreams, and templating would be a lot cleaner to implement.

E.g.:

template<class TYPE> void printCommonStatistics( TYPE & mystruct, int count )
{
  for( int i=0;  i<count;  i++ )
    cout << "FOO: " << mystruct._cnt[i] << endl;
}      /* Assumes all mystruct's have a "_cnt" member. */

But that's probably not what you are looking for...

C isn't my cup o'java, but I think your problem is that "void *cmncnt" should be CMNCNT *cmncnt.

Feel free to correct me now, C programmers, and tell me this is why java programmers can't have nice things.

This line is kind of tortured, don'tcha think?

CMNCNT *cmncnt = (CMNCNT *)&cmncntin[ii*cmncnt_elmsize];

How about something more like

CMNCNT *cmncnt = ((CMNCNT *)(cmncntin + (ii * cmncnt_elmsize));

Or better yet, if cmncnt_elmsize = sizeof(CMNCNT)

CMNCNT *cmncnt = ((CMNCNT *)cmncntin) + ii;

That should also get rid of the warning, since you are no longer dereferencing a void *.

BTW: I'm not real sure why you are doing it this way, but if cmncnt_elmsize is sometimes not sizeof(CMNCNT), and can in fact vary from call to call, I'd suggest rethinking this design. I suppose there could be a good reason for it, but it looks really shaky to me. I can almost guarantee there is a better way to design things.

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