Могу ли я делать то, что хочу, с выделенной памятью

Существуют ли ограничения на то, что я могу сделать с выделенной памятью? (по стандарту)

Например

#include <stdio.h>
#include <stdlib.h>

struct str{
    long long a;
    long b;
};

int main(void) 
{
    long *x = calloc(4,sizeof(long));
    x[0] = 2;
    x[3] = 7;
//is anything beyond here legal( if you would exclude possible illegal operations)
    long long *y = x; 
    printf("%lld\n",y[0]); 
    y[0] = 2;
    memset (x,0,16);
    struct str *bar = x;
    bar->b =  4;
    printf("%lld\n",bar->a); 
    return 0;
}

Подвести итоги:

  • Могу ли я преобразовать указатель в другие типы данных и структуры, если размер подходит?
  • Тогда я могу читать, прежде чем писать?
  • Если нет, могу ли я читать после того, как написал?
  • Могу ли я использовать его со структурой меньше выделенной памяти?

person Kami Kaze    schedule 26.01.2017    source источник


Ответы (3)


Чтение из y[0] нарушает строгое правило алиасинга. Вы используете lvalue типа long long для чтения объектов эффективного типа long.

Предполагая, что вы опускаете эту строку; следующая проблемная часть - memset(x,0,16);. В этом ответе утверждается, что memset не обновляет эффективный тип. Стандарт не ясен.

Предполагая, что memset оставляет эффективный тип без изменений; следующая проблема - чтение bar->a.

Стандарт C также неясен. Некоторые люди говорят, что bar->a подразумевает (*bar).a, и это строгое нарушение алиасинга, потому что мы не записали объект bar в местоположение сначала.

Другие (включая меня) говорят, что это нормально: единственное значение lvalue, используемое для доступа, — bar->a; это lvalue типа long long, и он обращается к объекту эффективного типа long long (тот, который написан y[0] = 2;).

Существует рабочая группа C2X, которая работает над улучшением спецификации строгого алиасинга, чтобы прояснить эти проблемы.

person M.M    schedule 26.01.2017
comment
6.5p6 подразумевает, что memove и компания изменяют действующий тип. - person StoryTeller - Unslander Monica; 26.01.2017
comment
@StoryTeller memmove и memcpy, безусловно, работают, но я не думаю, что memset подпадает под одно и то же понятие. Если вы скажете, что memset устанавливает эффективный тип (для char — что еще?), то распространенная идиома memset(x, 0, n); для инициализации нулями некоторых целых чисел приведет к UB, поэтому я думаю, что это не будет практической интерпретацией. - person M.M; 26.01.2017
comment
Итак, чтение или запись на bar->b будет в порядке, потому что это будет тот же псевдоним? - person Kami Kaze; 26.01.2017
comment
@M.M: Из того, что я видел, предложения C2X кажутся более сосредоточенными на добавлении еще большего количества ситуаций, когда компиляторы могут игнорировать потенциальные псевдонимы, а не на добавлении способов для программистов сказать эй, эти вещи могут быть псевдонимами. Если бы Стандарт допускал способы, с помощью которых программисты могли бы указывать, какие вещи могут быть псевдонимами, и определять различные уровни совместимости псевдонимов, тогда код, в котором отмечены все формы псевдонимов, можно было бы безопасно оптимизировать более агрессивно, чем в существующих правилах, но программистам не нужно было бы использовать -fno-strict-aliasing, чтобы все работало. - person supercat; 11.02.2017

Могу ли я преобразовать указатель в другие типы данных, если размер подходит?

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

Могу ли я читать, прежде чем писать?
Если нет, могу ли я читать после того, как написал?

Нет. За исключением случаев, когда указано иное (calloc — это иное)2, значение в памяти является неопределенным. Он может содержать значения прерывания. Приведение для повторной интерпретации значения как другого типа является UB и нарушением строгого алиасинга (6.5p7)

Могу ли я использовать его со структурой меньше выделенной памяти?

Да, но это пустая трата.


1 Сначала нужно выполнить приведение к void*. В противном случае вы получите обоснованную жалобу от компилятора на несовместимые типы указателей.
2 Даже в этом случае некоторые типы могут захватывать полностью 0-битный шаблон, так что это зависит от обстоятельств.

person StoryTeller - Unslander Monica    schedule 26.01.2017
comment
Хорошо с первым ответом, остальное сделано, так как они зависели от этой операции. - person Kami Kaze; 26.01.2017
comment
@KamiKaze - отредактировано, чтобы отразить комментарий MM о моем ржавом воспоминании о стандарте. - person StoryTeller - Unslander Monica; 26.01.2017

Большинство компиляторов предлагают режим, в котором чтение и запись указателей будут воздействовать на базовое хранилище в той последовательности, в которой они выполняются, независимо от вовлеченных типов данных. Стандарт не требует, чтобы компиляторы предлагали такой режим, но, насколько я могу судить, это делают все качественные компиляторы.

Согласно их опубликованному обоснованию, авторы Стандарта добавили в язык ограничения на псевдонимы с заявленной целью избежать пессимистичных предположений компиляторов о псевдонимах при заданном коде, например:

float f;
float test(int *p)
{
  f=1.0f;
  *p = 2;
  return f;
}

Обратите внимание, что в примере, приведенном в обосновании [очень похожем на вышеприведенное], даже если было законно изменить хранилище, используемое f, с помощью указателя p, разумный человек, глядя на код, не имел бы оснований полагать, что это вероятно. что-нибудь когда-нибудь случится. С другой стороны, многие разработчики компиляторов признали, что если дать что-то вроде:

float f;
float test(float *p)
{
  f=1.0f;
  *(int*)p = 2;
  return f;
}

нужно быть намеренно тупым, чтобы думать, что код вряд ли изменит хранилище, используемое float, и, следовательно, не было причин, по которым качественный компилятор не должен рассматривать запись в *(int*)p как потенциальную запись в float.

К сожалению, за прошедшие годы разработчики компиляторов стали все более агрессивно использовать «оптимизации» псевдонимов на основе типов, иногда таким образом, что они явно и бесспорно выходят за рамки того, что позволяет Стандарт. Если программе никогда не потребуется обращаться к какому-либо хранилищу как к разным типам в разное время, я бы предложил использовать параметр -fno-strict-aliasing для компиляторов, которые его поддерживают. В противном случае можно иметь код, который соответствует Стандарту и работает сегодня, но не работает в будущей версии компилятора, которая стала еще более агрессивной со своими «оптимизациями».

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

person supercat    schedule 10.02.2017
comment
Думаю, мне нужно немного прочитать, чтобы полностью понять ваш ответ. Спасибо за подробное описание. - person Kami Kaze; 13.02.2017
comment
@KamiKaze: почитайте restrict; это может обеспечить преимущества в производительности, даже если не отключить псевдонимы на основе типов, но может стать особенно важным, если это сделать. - person supercat; 13.02.2017