Пацвердзіць аргумент тыпу масіва ў C/C ++ папярэдняй апрацоўкі макрасаў на час кампіляцыі

Ці ёсць спосаб, каб праверыць на час кампіляцыі ў гр макракаманды, што аргумент з'яўляецца масівам?

напрыклад, у гэтых двух макрасаў:

#define CLEAN_ARRAY(arr) \
    do { \
        bzero(arr, sizeof(arr)); \
    } while (0)

і

#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0]))

Я спрабаваў нешта з дапамогай CTC (X) макрас , але не змог знайсці які-небудзь спосаб праверкі/папярэдзіць, калі обр не з'яўляецца масівам.

3
Так, але гэта можа быць 2 розныя падыходы.
дададзена аўтар 0x90, крыніца
@MatsPetersson калі ласка, вы можаце дадаць свой каментар у адказ?
дададзена аўтар 0x90, крыніца
SizeOf ((х) [0]) выдае памылку, калі тып не з'яўляецца «индексируемой» тып - аднак, калі гэта адбудзецца, будзе паказальнік, ён будзе шчасліва прыняць гэта. І, вядома, калі ёсць аператар [] для тыпу х .
дададзена аўтар Mats Petersson, крыніца
Якую праблему вы спрабуеце вырашыць? Вызначэнне нешта, ці з'яўляецца масіў, або атрымаць колькасць элементаў нешта ёсць?
дададзена аўтар juanchopanza, крыніца
@ 0x90: Для пытанняў, якія маюць розныя адказы на C і C ++, вы павінны задаць асобныя пытанні, таму што вы можаце пазначыць толькі адзін адказ, як прынята, але могуць быць асобныя адказы на асобныя мовы.
дададзена аўтар Eric Postpischil, крыніца
Я заўважыў, што вы ёсць і C і C ++ <�код /> тэгі. Ёсць некаторыя цікавыя падыходы шаблону ў C ++ . Вам патрэбен адказ, прыдатны для абедзвюх моў адразу ці што?
дададзена аўтар BoBTFish, крыніца
Для вашага ARRAY_SIZE макра вы можаце паглядзець на гэтым артыкуле (С ++ толькі).
дададзена аўтар gx_, крыніца

7 адказы

Вось рашэнне ў чыстым C, які не выклікае не нявызначанага паводзін:

#define IS_INDEXABLE(arg) (sizeof(arg[0]))
#define IS_ARRAY(arg) (IS_INDEXABLE(arg) && (((void *) &arg) == ((void *) arg)))

Калі вам неабходна пераканацца, што значэнне з'яўляецца масівам (гэта выклікаць памылку падчас кампіляцыі, калі няма), вы можаце проста выкарыстоўваць яго ў якасці инициализатора ў аператары пералічэнняў (або статычнай зменнай), напрыклад:

static int __ ## arg ## _is_array = IS_ARRAY(arg);//works for an array, fails for pointer.

Я не зусім упэўнены, што будзе адбывацца з VLA, але гуляць вакол трохі павінны знайсці, што адказ з даволі хутка.


Старыя адказы:

Так як гэта пазначана C (і GCC), я паспрабую рашэнне тут:

#define IS_ARRAY(arg) __builtin_choose_expr(__builtin_types_compatible_p(typeof(arg[0]) [], typeof(arg)), 1, 0)

Another solution, using C11's _Generic feature & typeof:

#define IS_ARRAY(arg) _Generic((arg),\
    typeof(arg[0]) *: 0,\
    typeof(arg[0]) [sizeof(arg)/sizeof(arg[0])]: 1\
)

У прынцыпе, усё гэта робіць выкарыстоўваць некаторыя фантазіі асаблівасці GCC, каб вызначыць, ці з'яўляецца тып аргументу сумясціць з масівам тыпу элементаў аргументу. Яна вяртае 0 або 1, і вы маглі б замяніць 0 з чымсьці, што стварае кампіляцыі памылкі часу, калі вы хочаце.

10
дададзена
@TS я сумняваюся, што якая-небудзь тэхнічная розніца ў дадзеным выпадку. Я напісаў гэты адказ 4 гады таму, але я думаю, што маё першапачатковае рашэнне (перад адпраўкай) удзелам некаторай дадатковая логікі, заснаваную на тым ці не ўдалася праверка тыпу. Затым я зразумеў, што дадатковыя праверкі былі вельмі залішне.
дададзена аўтар Richard J. Ross III, крыніца
@EricPostpischil я нічога не магу знайсці, у прыватнасці, пра гэта, акрамя тып выказвання павінен быць T (*) [памер] . Я хацеў бы выказаць здагадку, што ў большасці рэалізацый адрас застаецца ранейшым, але ў мяне няма ўвесь дзень, каб паліць стандарт.
дададзена аўтар Richard J. Ross III, крыніца
@EricPostpischil тэхнічна правільна: лепшы выгляд правільна. На жаль, я не думаю, што ёсць які-небудзь спосаб пра тое, што нават пры выкарыстанні дзівацтва з абмежаванне паказальнікаў.
дададзена аўтар Richard J. Ross III, крыніца
У цяперашні час першы метад у гэтым адказе добра для практычных мэтаў, але гэта тэхнічна няправільна, бо ён не для х , вызначанага з несапраўдных * х = & х; .
дададзена аўтар Eric Postpischil, крыніца
Што такое перавага __ builtin_choose_expr (..., 1,0) больш за ўсё __ builtin_types_compatible_p (...) ?
дададзена аўтар T S, крыніца

Чысты раствор С99:

enum { must_be_an_array_1 = ((void *) &(arr)) == ((void *) (arr)) };
typedef char must_be_an_array_2[((void *) &(arr)) == ((void *) (arr)) ? 1 : -1];

Гэта выкарыстоўвае той факт, што адрас масіва з'яўляецца такім жа, як адрас яго першага члена, і што член пералічэння павінен быць складовай канстантай. Калі кампілятар досыць разумны, каб сказаць, што паказальнік на а-паказальнік мае асаблівы адрас, ён будзе задыхацца на другім заяве.

Нам яшчэ трэба першае сцвярджэнне, бо ў адваротным выпадку кампілятар з падтрымкай масіваў памеру падчас выканання (напрыклад, GCC 4.7) будзе выконваць параўнанне адрасы падчас выканання і выклікаць нявызначаны паводзіны, паколькі памер масіва падчас выканання адмоўны (напрыклад, пры ССЗ праграма памылкі сегментацыі).

Поўная праграма:

#include 

#define CLEAN_ARRAY(arr) \
    do { \
        enum { must_be_an_array_1 = ((void *) &(arr)) == ((void *) (arr)) }; \
        typedef char must_be_an_array_2[((void *) &(arr)) == ((void *) (arr)) ? 1 : -1]; \
        bzero(arr, sizeof(arr)); \
    } while (0)

int main() {
    int arr[5];
    CLEAN_ARRAY(arr);
    int *ptr;
    CLEAN_ARRAY(ptr); //error: enumerator value for ‘must_be_an_array’ is not an integer constant
    return 0;
}
6
дададзена
@OliCharlesworth вядома, але гэты тып пытання будзе лёгка злоўлены з модульным тэставаннем.
дададзена аўтар Richard J. Ross III, крыніца
@EricPostpischil толькі таму, што не <�я> гарантавана </я> не азначае, што мы не можам рабіць здагадкі пра гэта. Калі вы жадаеце гуляць у гульню стандартаў, вы можаце зрабіць гэта на працягу ўсяго дня, але гэта сапраўды азначае, што дамкрат, калі кампілятар робіць нешта іншае.
дададзена аўтар Richard J. Ross III, крыніца
Я жартую чалавек, усё гэта CC-вікі ў любым выпадку. + +1.
дададзена аўтар Richard J. Ross III, крыніца
Nice Ніндзя-копія майго адказу :)
дададзена аўтар Richard J. Ross III, крыніца
Акрамя таго, ЬурейиЕ, здаецца, не патрабуюцца, калі вы проста шукаеце памылкі часу кампіляцыі (канстантнасцю-Нес з пералічэнняў инициализаторе для гэтага дастаткова)
дададзена аўтар M.M, крыніца
Тэкст у C11 6.6 «Сталыя выразы» не здаецца, што == аператар з двума канстантамі адраснымі вырабляе пастаяннае выраз прыдатны да ўжывання ў инициализаторе. Хоць гэты код працуе для мяне ў некаторых выпадках, яна павінна знаходзіцца пад/10 (г.зн. рэалізацыі могуць вызначаць іншыя).
дададзена аўтар M.M, крыніца
@OliCharlesworth абнаўляецца выкарыстоўваць == , які я думаю, што ўсё ў парадку.
дададзена аўтар ecatmur, крыніца
@ RichardJ.RossIII не на ўсіх, я працаваў на выкарыстанні == . Праблема ў мяне было з тым, што НКУ дазваляе масіваў падчас выканання памеру.
дададзена аўтар ecatmur, крыніца
@ M.M ды, падобна, што гэта не з'яўляецца дапушчальным арыфметычным канстанта пад 6.8. ЬурейеЕ патрабуецца, каб злавіць той выпадак, калі кампілятар дазваляе (па 6.10) == выраз для False .
дададзена аўтар ecatmur, крыніца
Я падазраю, што гэта выклікае UB, тэхнічна. Арыфметыка паказальнікаў вызначаецца толькі для падрадковых элементаў аднаго і таго ж аб'екта масіва. Але акрамя таго, гэта вельмі акуратна!
дададзена аўтар Oliver Charlesworth, крыніца
Я думаю, што гэта па-ранейшаму вызначаецца рэалізацыя (гл правілаў канстантных выразаў у раздзеле 6.6 p7).
дададзена аўтар Oliver Charlesworth, крыніца
@EricPostpischil: 6.5.9 p6 кажа «уключаючы паказальнік на аб'ект і подобъекту у яго пачатку» ... Не ведаю, як гэта ўзаемадзейнічае з кідком у несапраўдным * хоць.
дададзена аўтар Oliver Charlesworth, крыніца
@ RichardJ.RossIII: Для такога роду рэчаў, партатыўная рашэнне бясконца пераважней непортабельного рашэння, якое можа адбыцца збой бязгучна на некаторых платформах;)
дададзена аўтар Oliver Charlesworth, крыніца
@ RichardJ.RossIII: У дадзеным выпадку, гэта, верагодна, было б, ды (калі вы пабеглі ваш набор тэстаў з кожным кампілятарам/платформай вы былі зацікаўлены ў). Але ў цэлым, адзінкавыя тэсты дрэнны спосаб выяўлення UB (што гэта не так, каб быць справядлівым).
дададзена аўтар Oliver Charlesworth, крыніца
<�Р> Як праверыць у З макра аргумент тыпу ARRAY

Выкарыстоўвайце станд :: is_array ўнутры макраса. Ці забыцца themacro і проста выкарыстоўваць зЬй :: is_array .

Што тычыцца ARRAY_SIZE ,

constexpr size_t size(T const (&)[N])
{
  return N;
}
4
дададзена
@EricPostpischil я абнавіў пытанне адпаведным чынам.
дададзена аўтар 0x90, крыніца
@EricPostpischil ён адмыслова пытаецца аб C макрас, гэта нічога, што прывяло б мяне думаць, што гэта абмяжоўваецца C. У любым выпадку, калі ён пазначаны C ++, я дам адказ на C ++ не гавораць.
дададзена аўтар juanchopanza, крыніца
@EricPostpischil Калі вы праверыце каментары па гэтым пытанні, яны хочуць атрымаць адказы на абодва, і яны «могуць быць 2 розныя падыходы.» Так што гэта адказвае па меншай меры, палова пытання.
дададзена аўтар BoBTFish, крыніца

У C, гэта павінна працаваць:

#define VALIDATE_ARRAY(arr) (void)(sizeof((arr)[0]))

int *a, b;
int main() {
        VALIDATE_ARRAY(a);
        VALIDATE_ARRAY(b);
        return 0;
}

Гэтая праграма не будзе кампілявацца, таму што б не з'яўляецца масівам. Гэта адбываецца таму, што б [0] з'яўляецца некарэктным. Ён не будзе адрозніваць паказальнікі ад масіваў - Я не думаю, што вы можаце зрабіць гэта.

Гэтая форма макра можа быць выкарыстана толькі ўнутры функцыі. Калі вы хочаце выкарыстоўваць яго па-за функцыі, вам прыйдзецца змяніць яго (напрыклад, каб абвясціць Экстэрн масіў).

3
дададзена
Паказальнікі не зьяўляюцца масівамі, і масівы не зьяўляюцца паказальнікамі.
дададзена аўтар Richard J. Ross III, крыніца
Гэта не ў стане забяспечыць памылку пры обр з'яўляецца паказальнікам.
дададзена аўтар Eric Postpischil, крыніца

Наколькі я магу судзіць, пакуль ніхто не даў спосаб гарантаваць, што аргумент ARRAY_SIZE на самой справе масіў.

Edit:
Just found Array-size macro that rejects pointers

2
дададзена

У адпаведнасці з маім каментаром:

sizeof((x)[0]) will give an error if the type is not an "indexable" type - however, if it happens to be a pointer, it will happily take that. And also, if there is an operator[] for the type of x.

Гэта даволі цяжка ў C, каб зрабіць гэта, але C ++ можа дазволіць некаторыя рашэнні тыпу шаблону (я на самой справе не ведаю, як зрабіць гэта, так як я ніколі не спрабаваў зрабіць гэта, або што-небудзь падобнае з шаблонамі).

1
дададзена

(Для C ++) мой бягучы ясна макрас ў VS2010 з'яўляецца:

#define CLEAR(v)    do { __pragma(warning(suppress: 4127 4836)) typedef std::remove_reference< decltype(v)>::type T; static_assert( std::is_pod::value || (__has_trivial_constructor(T) && __has_trivial_destructor(T)), "must not CLEAR a non-POD!" ); static_assert( !std::is_pointer::value, "pointer passed to CLEAR!" );  memset(&(v), 0, sizeof(v)); } while(0)

Вы можаце зрабіць свой варыянт, выкарыстоўваючы матэрыял у type_traits загалоўку.

Фарматаваны verision з тлумачэннямі:

#define CLEAR(v) \
do { \ 
    __pragma(warning(suppress: 4127 4836)) \
    typedef std::remove_reference< decltype(v)>::type T; \
    static_assert( \
        std::is_pod::value \
        || (__has_trivial_constructor(T) && __has_trivial_destructor(T)), \
        "must not CLEAR a non-POD!" ); \
     static_assert( !std::is_pointer::value, "pointer passed to CLEAR!" ); \
     memset(&(v), 0, sizeof(v)); \
  } while(0)

знешняе рабіць-то час, каб зрабіць яго прыдатным для выкарыстання як сапраўдная функцыя ва ўсіх месцах, у тым ліку, калі/іншы.

Remove_reference is needed so it works with lvalues, decltype alone makes int* and int*& different and is_pointer reports false for the latter.

Праверка is_pod добрая для агульнага, дадатковая ўмова дазваляе STRUCT A1: A; выпадак працы, дзе А POD і A1 толькі дадае больш членаў POD. Для is_pod мэты гэта хлусня, але ясна, што робіць той жа сэнс.

is_pointer праверка ахоўнікаў чаканай памылкі друку, калі вы атрымліваеце Ускосна няправільна на паказальніку або перадаць адрас структуры ў замяшанні. Выкарыстоўвайце = NULL, каб ачысціць паказальнік калі ласка. ;-)

<�Код> __ Прагма ёсць для падаўлення папярэджанняў L4, якія выдаюцца ў адваротным выпадку.

0
дададзена
Гэта можа быць вялікім, але гэта ў цяперашні час немагчыма чытаць. Калі ласка, фарматаваць яго!
дададзена аўтар Oliver Charlesworth, крыніца