GCC menga C99 konst tizmasining maydonlarini o'zgartirish haqida ogohlantiradimi?

Konst-kodni tuzishga harakat qilayotib, kichik bir mavzu ustida qoqildim.

Men konstruktsiyaga ishora qiluvchi funksiyani yozishni xohlagan bo'lardim, derazaga "menga tuzatishni o'zgartiradimi yoki yo'qmi deb ayt, iltimos, men istamayman".

Birdan qo'zg'alonchi menga buni bajarishga imkon beradi deb o'ylayman:

struct A
{
    char *ptrChar;
};

void f(const struct A *ptrA)
{
    ptrA->ptrChar[0] = 'A';//NOT DESIRED!!
}

Qaysi narsalar tushunarli, chunki aslida konstruktsiya ko'rsatgichning o'zi emas, balki u ko'rsatadigan turdagi emas. Men derazachi menga, men buni xohlamayotgan narsani qilyapman, desa ham mumkinligini aytishni istayman.

Men derleyici sifatida gcc foydalanardim. Yuqoridagi kodning qonuniy bo'lishi kerakligini bilsam-da, men hali ham ogohlantirish berishi mumkinligini tekshirganman, lekin hech narsa kelmadi. Mening buyruq satrim:

gcc -std=c99 -Wall -Wextra -pedantic test.c

Bu masalani hal qilish mumkinmi?

37
qo'shib qo'ydi muallif M.M, manba
bu holat a'zo tayinlash uchun ishlaydi. Masalan, ptrA-> ptrChar = malloc (2); Qaysi a'zoning ishora qilayotgani shunday emas.
qo'shib qo'ydi muallif BLUEPIXY, manba
Buning uchun odatda struct a va a_do_something (const struct * a); kabi ba'zi funktsiyalar deklaratsiyalarini e'lon qilgan, code> struct A nom faylida kamdan-kam talab qilinadi. f faqatgina stuff = a_do_something (ptrA); kabi faqat ptrA foydalanadi. >
qo'shib qo'ydi muallif Gábor Buella, manba

7 javoblar

Buning uchun zarur bo'lgan yo'lni loyihalashtirishning usuli bir xil ob'ekt uchun ikki xil turdagi: bitta o'qish/yozish turi va faqat bitta o'qish turi.

typedef struct
{
  char *ptrChar;
} A_rw;

typedef struct
{
  const char* ptrChar;
} A_ro;


typedef union
{
  A_rw rw;
  A_ro ro;  
} A;

Funktsiya ob'ektni o'zgartirishi kerak bo'lsa, o'qish-yozish turini parametr sifatida oladi, aks holda u faqat o'qiladi turni oladi.

void modify (A_rw* a)
{
  a->ptrChar[0] = 'A';
}

void print (const A_ro* a)
{
  puts(a->ptrChar);
}

Chaqiruvchi interfeysi yoqimli bo'lib, uni izchil qilish uchun ADT-ga umumiy interfeys sifatida sarmalovchi funktsiyalardan foydalanishingiz mumkin:

inline void A_modify (A* a)
{
  modify(&a->rw);
}

inline void A_print (const A* a)
{
  print(&a->ro);
}

Ushbu usul yordamida A endi qo'ng'iroq qiluvchining dasturini yashirish uchun opak turi sifatida amalga oshirilishi mumkin.

19
qo'shib qo'ydi
@Chris Xayes Men buni aniqladi. Bu juda aniq va menimcha, Lundin bu bilan yaxshi.
qo'shib qo'ydi muallif Peter A. Schneider, manba
Men ittifoqni yaxshi ko'raman va amalda ishlashini juda yaxshi bilaman; ruxsat beriladimi? Ar struktura turlari ar, mos, mos kelmaydigan.
qo'shib qo'ydi muallif Peter A. Schneider, manba
Ha, matn terish xatosi edi, uni aniqlash va aniqlash uchun rahmat!
qo'shib qo'ydi muallif Lundin, manba
@MartinBonner Ushbu izoh bilan nimani nazarda tutsam, avval derivatning birlashtiruvchi ikkita tuzilishini berasiz, ularning har biri mos keladigan ko'rsatgich turi mavjud. Har bir struktni boshqacha tarzda ajratishning o'zi yo'q, nima uchun? Har bir struct bir xil xotira tartibiga ega. Va manzilni saqlab qo'yganingizda, u strstst saralashida bo'lishidan qat'iy nazar, xuddi shu tarzda saqlanadi. Umumiy ma'noda, bu ma'lumotlar turi ishlashi kerakligini va boshqa hech qanday dasturning mantiqiy emasligini belgilaydi. Bu erda standart hech qanday ahamiyatga ega emas, chunki aytadigan hech narsa haqiqatni o'zgartira olmaydi.
qo'shib qo'ydi muallif Lundin, manba
@MartinBonner Men qat'iy taktika muammosi emasligiga ishonaman. "Ob'ektni saqlangan qiymat faqat quyidagi turlardan biriga ega lvalu ifodasi orqali ochilishi kerak:" /-/ "samarali turiga mos keluvchi turning malakali versiyasi "a'zolari orasida yuqorida aytib o'tilgan turlardan birini o'z ichiga olgan jami yoki birlashma turi (shu jumladan, yineleyici, subaggregat yoki biriktirilgan ittifoq a'zosi)" Qavslar ichidagi matnni to'g'ri tushunaman, kodim yaxshi ekanligini aytadi.
qo'shib qo'ydi muallif Lundin, manba
@ PeterA.Schneider Men shuni ishonamanki, "qattiq aliasing qoidasi" (6.5/7) va "turdagi punning qoidasi" (6.2.6.1/7) ittifoqi. Ammo ikkinchisiga qaraganda, bu aniqlanmagan xatti-harakatlar bo'lishi mumkin. Buning ahamiyati yo'q, chunki siz aytganidek, bu har doim amalda ishlaydi. Derleyicinin standartda hech qanday nozik qoidalar bo'lishidan qat'iy nazar, ushbu kodni kutilganidan boshqacha tarzda amalga oshirish uchun qanday ahamiyatga ega ekanligini tushunmayapman. Ammo C ++ da ishlamasligi mumkin, chunki iirc C ++ kasaba uyushmalaridan foydalanishga ruxsat bermaydi.
qo'shib qo'ydi muallif Lundin, manba
@Lundin: ehtiyot bo'ling gcc. Bu qat'iy taktika qoidasidan kelib chiqadigan taxminlarga asoslangan optimallashtirishni juda agressiv.
qo'shib qo'ydi muallif Martin Bonner, manba
Ha. O'ylaymanki, siz haqsiz. "Men amalda ishlashim kerak", degan xavotirda meni nima xavotirga solgan bo'lsa, - agar standart talab qilinsa, bu (vaqti-vaqti bilan emas).
qo'shib qo'ydi muallif Martin Bonner, manba
Muammo const ko'rsatkichi orqali hech qachon yozmasligingiz kerak bo'lgan kompilyator joylari birinchi o'qishni keshlashi va noto'g'ri bo'lmagan ko'rsatgich orqali barcha yozuvlarni e'tiborsiz qoldirishi mumkin. Siz keltirgan me'yorning bo'limi bu muayyan holatda buni qilolmaydi degan ma'noni anglatadi.
qo'shib qo'ydi muallif Martin Bonner, manba
"Agar funktsiya ob'ektni o'zgartirishi kerak bo'lsa, o'qish turi faqat parametr sifatida qabul qilinadi, aks holda u o'qish-yozish turini oladi". Men bu yozuvni yozmoqchiman va bu rollar farqlanadi.
qo'shib qo'ydi muallif Chris Hayes, manba
Bundan tashqari, men qanday qilib bu makrolarni ishlatishni yaxshi ko'rmagandim, deb o'ylardim. S-chiroyli toza kod!
qo'shib qo'ydi muallif larkey, manba

Bu - amalga oshirish va boshqalar interfeysi yoki "axborotni yashirish" o'rniga - yoki yashirin bo'lmagan ;-) masalan. C ++ dasturida faqatgina markerni ko'rsatib, moslashuvchan ijtimoiy konstruktsiyani aniqlaydi. Yoki abstrakt sinf - "interfeys" - aksessuar bilan belgilanadi. Buni amalga oshirish uchun strukturani amalga oshirish kerak edi. Yapiciy misollarni yaratishga hojat bo'lmagan foydalanuvchilar faqat interfeyslarni ko'rishlari kerak.

C-da parametr sifatida strukturaga markerni olib boruvchi funktsiyani belgilab qo'yib, uni chizish uchun markerni qaytaradi. Ushbu tuzilmalarning misollarini yaratmaydigan foydalanuvchilar uchun, hatto, tizimning bajarilishiga moyil bo'lmagan, faqat funktsiyalarni qabul qilish (yoki fabrika kabi) ko'rsatgichlari bilan ishlashni belgilaydigan "foydalanuvchi sarlavhasi" ni ham berishi mumkin. Bu esa, tuzilmaning to'liq bo'lmagan turini (faqat misollarni ko'rsatish uchun ishlatilishi mumkin) qoldiradi. Ushbu naqsh C ++ ning bu this ko'rsatkichi bilan sahnada orttirgan narsalarni samarali ravishda emulyatsiya qiladi.

7
qo'shib qo'ydi

Bu C tilining ma'lum bir muammosi va uni oldini olish mumkin emas. Shunday qilib, siz tuzilmani o'zgartirmaysiz, tuzilmadan olingan const -qualified pointer orqali alohida ob'ektni o'zgartirasiz. const semantik dastlab mantiqiy programmalar uchun emas, balki jismonan yoziladigan emas xotira joylarini belgilash kerakligi atrofida yaratilgan.

5
qo'shib qo'ydi

Ehtimol C11 ni ishlatishga qaror qilsangiz, bir xil maqolaning doimiy yoki o'zgaruvchan versiyasiga taaluqli bo'lgan umumiy so'lni qo'llashingiz mumkin (siz o'z tarkibida birlashmani qo'shishingiz kerak). Shunga o'xshash narsa:

struct A
{
    union {
        char *m_ptrChar;

        const char *m_cptrChar;
    } ;
};

#define ptrChar_m(a) _Generic(a, struct A *: a->m_ptrChar,        \
                                 const struct A *: a->m_cptrChar)//,  \
                                 //struct A: a.m_ptrChar,        \
                                 //const struct A: a.m_cptrChar)

void f(const struct A *ptrA)
{
    ptrChar_m(ptrA) = 'A';//NOT DESIRED!!
}

Ittifoq bir a'zo uchun 2 talqinni yaratadi. m_cptrChar doimiy xarf va m_ptrChar uchun doimiy bo'lmagan belgilar. So'ngra uning makrosini uning parametr turiga qarab qaratish kerak bo'lgan qarorga qaror qiladi.

Yagona muammo shuki, makro ptrChar_m faqat ikkalasini emas, balki, bu strukturaning ko'rsatgichi yoki ob'ekti bilan ishlashi mumkin.

5
qo'shib qo'ydi
Iltimos, buni qilmang. Bu dahshatli. Hech kim buni bir yil ichida, hatto siz ham tushunmaydi.
qo'shib qo'ydi muallif fuz, manba
@FUZxxl Bu nuqta. Murakkab ish kodlari men uchun eng yoqimlidir. Garchi ushbu echim, ehtimol, eng yaxshi emas (mening shaxsiy fikrim).
qo'shib qo'ydi muallif AnArrayOfFunctions, manba
Yaxshi echim bo'lsa-da, siz . holatlarini tashlab ketishingiz kerak yoki siz so'llarni kengaytirish bilan bog'liq muammolarga duch kelasiz. Bundan tashqari, pastki belgilar bilan boshlanadigan o'zgaruvchining nomlarini ham chetlab o'ting (7.1.3).
qo'shib qo'ydi muallif Lundin, manba
Men ittifoqdoshlik punktini yoqtirmayman. Buning o'rniga biz _Generic (a, struct A *: a-> ptrChar, const struct A *: (const char *) a-> ptrChar) dan foydalana olamizmi? Shunday qilib, biz strukturaning ta'rifini o'zgartirishga hojat yo'q.
qo'shib qo'ydi muallif chi, manba

Ba'zi "accessor" funksiyalarining orqasida ma'lumotlarni yashirishimiz mumkin:

// header
struct A;          //incomplete type
char *getPtr(struct A *);
const char *getCPtr(const struct A *);

// implementation
struct A { char *ptr };
char *getPtr(struct A *a) { return a->ptr; }
const char *getCPtr(const struct A *a) { return a->ptr; }
3
qo'shib qo'ydi

Yo'q, agar siz strukturaning ta'rifini o'zgartirsangiz:

struct A
{
    const char *ptrChar;
};

Qadimgi strukturaning buzilmasligini saqlaydigan yana bir ixchamlashtirilgan yechim, mos belgilar a'zolari o'rnatiluvchi bir xil a'zolari bo'lgan yangi strukturani belgilashdan iborat: constning turiga ishora qiladi. So'ngra siz chaqirayotgan funktsiya yangi tuzilishni olish uchun o'zgartiriladi. Qadimgi strukturani oladi, a'zo strukturaga a'zo tomonidan yangi strukturaga a'zo bo'lib kiradi va uni funktsiyaga o'tkazadi.

2
qo'shib qo'ydi
Men a'zolarning kv-malakasiga faqat bir xil bo'lgan strukturaga oddiy shikastlanish UB bo'ladimi?
qo'shib qo'ydi muallif Peter A. Schneider, manba
@ PeterA.Schneider Ha, bunday turlari mos kelmaydi.
qo'shib qo'ydi muallif 2501, manba

GCC menga C99 konstruktsiyasi konstruktsiyasini o'zgartirish haqida ogohlantiradimi?

Const struct maydonlarini o'zgartirmaysiz.

A value of struct A contains a pointer to a non-const char. ptrA is a pointer to a const struct A. So you can't change the struct A value at *ptrA. So you can't change the pointer to char at (*ptrA).Char aka ptrA->ptrChar. But you are changing the value at where ptrA->ptrChar points, ie the value at *(ptrA->Char) aka ptrA->Char[0]. The only consts here are struct As and you're not changing a struct A so what exacty is "not desired"?

Agar o'zgarishlarni o'zgartirishga ruxsat berishni xohlamasangiz, struct A ning Char maydoni nuqtasi (bu A strukturasi orqali) foydalanganda foydalaning

struct A
{
    const char *ptrChar;//or char const *ptrChar;
};

Ehtimol, f ichida qilayotgan fikrlari o'xshash narsadir

void f(const struct A *ptrA)
{
    const char c = 'A';
    ptrA->ptrChar = &c;
}

Qaysi kompilyatordan xato bo'ladi.

1
qo'shib qo'ydi