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

Предположим, у меня есть эти структуры в c++:

class A{
  public:
    B b;
}

class B{
  public:
    C c;
}

class C{
  public:
    double x;
    double y;
    double z;
    double s;
    function Usize(){
      s  = sqrt(pow(x,2) + pow(y,2) + pow(z,2));
    }
}

Будет ли доступ к значениям в c в десять раз потребует больше памяти, чем создание прямого указателя на c и его использование? В терминах кода (принимая допустимые значения):

double dx = 2*rand()-1;
double dy = 2*rand()-1;
double dz = 2*rand()-1;

a->b->c.x *= dx;
a->b->c.y *= dy;
a->b->c.z *= dz;

if (a->b->c.x > 10) a->b->c.x -= 10;
else if (a->b->c.x <0) a->b->c.x += 10;
if (a->b->c.y > 10) a->b->c.y -= 10;
else if (a->b->c.y < 0) a->b->c.y += 10;
if (a->b->c.z > 10) a->b->c.z -= 10;
else if (a->b->c.z < 0) a->b->c.z += 10;

a->b->c->Usize();

против

double dx = 2*rand()-1;
double dy = 2*rand()-1;
double dz = 2*rand()-1;


C* ac = a->b->c
ac.x *= dx;
ac.y *= dy;
ac.z *= dz;

if (ac.x > 10) ac.x -= 10;
else if (ac.x < 0)  ac.x += 10;
if (ac.y > 10) ac.y -= 10;
else if (Ac.y < 0) ac.y += 10;
if (ac.z > 10) ac.z -= 10;
else if (ac.z < 0) ac.z += 10;

Благодарю.

3 ответа

Решение

В этом конкретном случае хороший компилятор должен быть в состоянии устранить общее выражение и сгенерировать в значительной степени оптимальный код. Так как вы получаете доступ к примитивным типам, a->b->c может быть оценен один раз и использован в методе.

Вызов C::USize() или доступ не примитивного типа в "классе C" нарушит этот шаблон и заставит компилятор переоценить a-> b-> c для следующей строки.

a->b->c.x = 10;
a->b->c.Usize();   // <-- Usize() may change a.b so the next line references another B.
a->b->c.y = 5;

Это потому, что компилятор не может на 100% быть уверенным, что вызов метода / оператор не изменяет a, ab или bc, поэтому он должен переоценить цепочку, чтобы убедиться.

Я бы почти назвал беспокойство об этом на данном этапе преждевременной оптимизацией. Тем не менее, ваш второй пример является более читабельным и помогает компилятору не догадываться, если вы вставите какие-либо вызовы методов позже, так что я бы пошел на это.

Шансов нет. Там не будет никакой разницы.

Хотя верно, что разыменование цепочки приведет к большему количеству обращений к памяти, современные компиляторы способны делать именно то, что вы сделали. (То есть преобразуйте свой первый пример во второй.)

Это происходит из-за стандартной оптимизации компилятора, называемой Common Subexpression Elission (CSE).

Название в значительной степени говорит обо всем. В вашем первом примере a->b->c это общее подвыражение, которое будет оптимизировано компилятором. Он будет оценен только один раз, результат сохранен и использован повторно для всех необходимых случаев.


Существует ряд ситуаций, которые могут помешать компилятору выполнять такие оптимизации.

  1. Если какие-либо соответствующие переменные объявлены volatile, то эта оптимизация не допускается, так как volatile переменная требует, чтобы она перезагружалась при каждом использовании.
  2. Если какая-либо из соответствующих переменных (или потенциально) модифицирована, то такая оптимизация не допускается, поскольку она может привести к другому результату.

Как примечание, ваш второй пример также более читабелен, поскольку есть цепочка разыменования.
Так что, если бы мне пришлось выбирать, какой использовать, я бы все равно пошел со вторым примером.

Теоретически это не будет иметь значения.

Любой современный оптимизатор должен переводить точно такой же код.

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