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

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

  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();
    

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


person Andrew    schedule 27.09.2010    source источник


Ответы (2)


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

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

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

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

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

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

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

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

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

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

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

Стандарт дает пример, который делает это довольно ясным (я думаю, что ясность 4 коротких абзацев определения restrict соответствует правилам разрешения имен 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
    }
}
person Michael Burr    schedule 27.09.2010

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

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

person Vijay Mathew    schedule 27.09.2010
comment
Я не думаю, что компиляторы могут игнорировать volatile. - person Michael Burr; 27.09.2010
comment
Компиляторы, как правило, могут игнорировать volatile, если реализация не поддерживает какие-либо асинхронные сигналы (и, следовательно, обработчики сигналов), которые могут изменить значение переменной вне нормального потока программы, или другие функции, определяемые реализацией (например, потоки или отображаемое в память устройство). IO), где volatile может иметь значение. - person R.. GitHub STOP HELPING ICE; 27.09.2010
comment
@R: я полагаю, что вы ошибаетесь, поскольку реализация не может «не поддерживать ввод-вывод с отображением памяти — такой ввод-вывод просто существует независимо от компилятора... - person einpoklum; 09.04.2013
comment
@einpoklum: некоторые процессоры используют специальные инструкции (часто с собственным выделенным адресным пространством) для ввода-вывода и не имеют ничего, кроме памяти, в любой части адресного пространства, до которой могут добраться указатели. В таких инструкциях ввод-вывод обычно реализуется либо через встроенный компилятор, либо путем ссылки на код, написанный на другом языке. Например, большинство вариантов 8031 ​​имеют 128-байтовое адресное пространство для ввода-вывода, доступ к которому можно получить только с помощью инструкций прямого режима; нет инструкций, которые позволили бы коду записывать в регистр ввода-вывода, идентифицируемый указателем. - person supercat; 24.08.2015
comment
Комментарий @einpoklum и голосование за него демонстрируют разницу между программистами на C, которые программируют вне типичной размещенной реализации в типичной операционной системе, и теми, кто этого не делает. Могут быть компиляторы, которые предполагают, что их целью является одноядерный однопоточный простой процессор без операционной системы, без переключателей контекста, без переходов к обработчикам прерываний и без ввода-вывода, если только вы не напишете свой собственный код, чтобы вручную делать эти вещи как часть нормального четко определенного потока управления C. - person mtraceur; 07.02.2021