Вопросы с ограниченным указателем

Я немного запутался в правилах, касающихся ограниченных указателей. Может быть, кто-то может мне помочь.

  1. Законно ли определять вложенные ограниченные указатели следующим образом:

    int* restrict a;
    int* restrict b;
    
    
    a = malloc(sizeof(int));
    
    
    // b = a; <-- assignment here is illegal, needs to happen in child block
    // *b = rand();
    
    
    while(1)
    {
        b = a;  // Is this legal?  Assuming 'b' is not modified outside the while() block
        *b = rand();
    }
    
  2. Допустимо ли выводить значение ограниченного указателя следующим образом:

    int* restrict c;
    int* restrict d;
    
    
    c = malloc(sizeof(int*)*101);
    d = c;
    
    
    for(int i = 0; i < 100; i++)
    {
        *d = i;
        d++;
    }
    
    
    c = d; // c is now set to the 101 element, is this legal assuming d isn't accessed?
    *c = rand();
    

Спасибо! Эндрю

2 ответа

Решение

Для справки, вот restrict довольно запутанное определение классификатора (из C99 6.7.3.1 "Формальное определение ограничения"):

Пусть D будет объявлением обычного идентификатора, который предоставляет средство для обозначения объекта P в качестве ограниченно-квалифицированного указателя на тип T.

Если D появляется внутри блока и не имеет внешнего класса хранения, пусть B обозначает блок. Если D появляется в списке объявлений параметров определения функции, пусть B обозначает связанный блок. В противном случае пусть B обозначает блок main (или блок любой функции, вызываемой при запуске программы в автономной среде).

В последующем выражение указателя E называется основанным на объекте P, если (в некоторой точке последовательности при выполнении B до оценки E) модифицировать P так, чтобы он указывал на копию объекта массива, на который он ранее указывал изменит значение E. Обратите внимание, что "based" определяется только для выражений с типами указателей.

Во время каждого выполнения B пусть L будет любым lvalue, имеющим & L на основе P. Если L используется для доступа к значению объекта X, который он обозначает, и X также изменяется (любым способом), то применяются следующие требования: T не должен быть постоянным. Каждое другое lvalue, используемое для доступа к значению X, также должно иметь свой адрес на основе P. Каждый доступ, который изменяет X, должен также рассматриваться как модифицирующий P, для целей этого подпункта. Если P присваивается значение выражения E указателя, которое основано на другом объекте ограниченного указателя P2, связанном с блоком B2, то либо выполнение B2 должно начинаться до выполнения B, либо выполнение B2 должно заканчиваться до назначение. Если эти требования не выполняются, то поведение не определено.

Здесь выполнение B означает ту часть выполнения программы, которая соответствует времени жизни объекта со скалярным типом и длительности автоматического хранения, связанной с B.

Мое прочтение вышеизложенного означает, что в вашем первом вопросе a не может быть назначен bдаже внутри дочернего блока - результат не определен. Такое назначение может быть сделано, если b были объявлены в этом "подблоке", но так как b объявляется в том же объеме, что и a, назначение не может быть сделано.

Для вопроса 2, задания между c а также d также приводят к неопределенному поведению (в обоих случаях).

Соответствующий бит из стандарта (для обоих вопросов):

Если P присваивается значение выражения E указателя, которое основано на другом объекте ограниченного указателя P2, связанном с блоком B2, то либо выполнение B2 должно начинаться до выполнения B, либо выполнение B2 должно заканчиваться до назначение.

Поскольку ограниченные указатели связаны с одним и тем же блоком, блок B2 не может начинаться до выполнения B, или завершаться B2 до назначения (поскольку B и B2 являются одним и тем же блоком).

Стандарт дает пример, который проясняет это (я думаю - ясность restrict 4 коротких абзаца определения соответствуют правилам разрешения имен в C++:

ПРИМЕР 4:

Ограничения правил для ограниченных указателей не различают вызов функции и эквивалентный вложенный блок. За одним исключением, только "внешние-внутренние" назначения между ограниченными указателями, объявленными во вложенных блоках, имеют определенное поведение.

{
    int * restrict p1;
    int * restrict q1;

    p1 = q1; //  undefined behavior

    {
        int * restrict p2 = p1; //  valid
        int * restrict q2 = q1; //  valid
        p1 = q2; //  undefined behavior
        p2 = q2; //  undefined behavior
    }
}

restrict Спецификатор типа указывает компилятору, что, если память, адресованная restrict квалифицированный указатель изменен, никакой другой указатель не получит доступ к той же памяти. Компилятор может решить оптимизировать код, включающий restrict указатели, которые в противном случае могут привести к некорректному поведению. Программист несет ответственность за то, чтобы указатели с ограниченным доступом использовались так, как они предназначались для использования. В противном случае может возникнуть неопределенное поведение. ( ссылка)

Как видно из приведенного выше описания, оба ваших назначения недопустимы, что может работать в исполняемых файлах, созданных одними компиляторами, но нарушаться в других. Не ожидайте, что сам компилятор выдаст ошибки или предупреждения как restrict просто дает возможность выполнить определенную оптимизацию, которую он может не выполнять, как в случае volatile,

Другие вопросы по тегам