Может ли использование указателей вызвать засорение памяти?
Предположим, у меня есть эти структуры в 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
это общее подвыражение, которое будет оптимизировано компилятором. Он будет оценен только один раз, результат сохранен и использован повторно для всех необходимых случаев.
Существует ряд ситуаций, которые могут помешать компилятору выполнять такие оптимизации.
- Если какие-либо соответствующие переменные объявлены
volatile
, то эта оптимизация не допускается, так какvolatile
переменная требует, чтобы она перезагружалась при каждом использовании. - Если какая-либо из соответствующих переменных (или потенциально) модифицирована, то такая оптимизация не допускается, поскольку она может привести к другому результату.
Как примечание, ваш второй пример также более читабелен, поскольку есть цепочка разыменования.
Так что, если бы мне пришлось выбирать, какой использовать, я бы все равно пошел со вторым примером.
Теоретически это не будет иметь значения.
Любой современный оптимизатор должен переводить точно такой же код.