Есть ли техническая причина использовать> (<) вместо! = При увеличении на 1 в цикле for?


198

Я почти никогда не вижу такой forпетли:

for (int i = 0; 5 != i; ++i)
{}

Есть ли техническая причина для использования >или <вместо !=увеличения на 1 в forцикле? Или это скорее конвенция?


104
Это делает его более читабельным, плюс, если вы когда-нибудь переключитесь i++на i+=2(например), он будет работать очень долго (или, возможно, навсегда). Теперь, поскольку вы обычно используете <для тех случаев, когда вы увеличиваете итератор более чем на 1, вы также можете использовать <и для случая, когда вы увеличиваете его на 1 (для согласованности).
Барак Манос

112
5 != iэто зло
Слава

27
Вы всегда должны осознавать, что вы делаете, но вы все равно будете делать ошибки. Использование <уменьшает вероятность того, что кто-то внесет ошибку в этот код.
Аураст

40
@OleTange Если вы выполняете итерацию с использованием чисел с плавающей запятой, то вы уже ввернуты ..
CaptainCodeman

29
@ Слава ... не зло. Вы очевидно никогда не были немного в C простой опечаткой, делающей разницу между if ( i == 5 ) ...и if ( i = 5 ). Совсем разные вещи. Обращение операндов предотвращает проблему: поскольку литералы не являются lvals, они не могут быть назначены, и компилятор бросает на опечатку. @Zingam: хорошее оборонительное программирование!
Николас Кэри

Ответы:


303
while (time != 6:30pm) {
    Work();
}

Сейчас 6:31 вечера ... Черт, теперь мой следующий шанс вернуться домой завтра! :)

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


25
Иногда попытка «снизить риски» просто приводит к сокрытию ошибки. Если конструкция цикла должна предотвратить незаметное проскальзывание 18:30, тогда использование <скрывает ошибку, но использование !=делает очевидным, что есть проблема.
Адриан Маккарти

27
@AdrianMcCarthy: не обязательно; он может просто ввести вторую ошибку, делая диагноз сложнее .
Гонки легкости на орбите

4
@AdrianMcCarthy: Это моя точка зрения; у вас могло бы быть несколько случаев этого, которые вы еще не видели;)
Гонки Легкости на Орбите

6
Я думаю, что итераторы - прекрасный пример точки @AdrianMcCarthy. Для них рекомендуется сравнение! = Вместо <.
Taekahn

5
Я видел именно эту ошибку (и это вызвало 3 месяца отладки) - но этот ответ совершенно не соответствует сути. В данном примере невозможно пропустить конечное условие. Вы могли бы даже сказать, что сознательно выбирать! = Более <означает строгое утверждение этого факта. Снижение риска, которого окончательно не существует, является для меня запахом кода. (Сказав это, вы также можете утверждать, что <идиоматичен, что это такая же веская причина использовать его, как и любое
другое

95

Там нет технической причины. Но есть снижение риска, ремонтопригодность и лучшее понимание кода.

<или >являются более строгими ограничениями, чем !=и выполняют ту же самую цель в большинстве случаев (я бы даже сказал, во всех практических случаях).

Существует дубликат вопрос здесь ; и один интересный ответ .


6
Есть некоторые типы, такие как итераторы, которые не поддерживают <или>, но поддерживают == и! =.
Саймон Б.

1
Это цикл с int . Нет итераторов.
Ely

7
«Но есть снижение риска, ремонтопригодность и лучшее понимание кода». Все это, по сути, технические причины. :-)
TJ Crowder

@TJCrowder Какая причина, когда вы просто хотите поговорить о том, что машины будут делать в результате?
Brilliand

1
@DanSheppard Я бы обычно рассматривал снижение риска и ремонтопригодности как деловые причины, а лучшее понимание кода как социальную причину (хотя в данном случае все эти причины применяются к техническому вопросу). Соображения «снижения риска» никоим образом не ограничиваются техническими вопросами, и соображения «лучшего понимания кода» могут измениться, если вы обнаружите, что работаете с другой группой людей (даже если задействованные машины вообще не меняются) ).
Brilliand

85

Да, есть причина. Если вы напишите (обычный старый индекс на основе) для цикла, как это

for (int i = a; i < b; ++i){}

тогда он работает, как и ожидалось, для любых значений aи b(т.е. нулевых итераций, когда a > bвместо бесконечных, если вы использовали i == b;).

С другой стороны, для итераторов вы бы написали

for (auto it = begin; it != end; ++it) 

потому что любой итератор должен реализовывать operator!=, но не для каждого итератора можно предоставить operator<.

Также на основе диапазона для петель

for (auto e : v)

это не просто сахар, но они заметно уменьшают шансы написать неправильный код.


4
Хороший пример. Я был бы заинтересован в случае !=вместо <. Я лично никогда не использовал это, я думаю.
Ely

@Elyasin Я не нашел хорошего, и я не хотел публиковать плохой: представьте, что у вас есть какой-то круговой контейнер, в котором вы увеличиваете на + x по модулю размера контейнера N и хотите остановить цикл, когда вы достичь определенного показателя .... это действительно плохой пример. Может быть, я найду лучше
idclev 463035818

Тот же цикл с! = Вместо <проясняет, в чем преимущество <(или>) в этом контексте. for (int i=a; i != b; i++) {}
Пол Смит

10
Использование чего-либо кроме !=итераторов довольно опасно, потому что иногда итераторы могут быть меньше, чем сравниваемые (это, например, указатель), но сравнение не имеет смысла (это связанный список).
Zan Lynx

1
Научитесь использовать пробелы, серьезно.
Майлз Рут

70

Вы можете иметь что-то вроде

for(int i = 0; i<5; ++i){
    ...
    if(...) i++;
    ...
}

Если ваша переменная цикла записана внутренним кодом, i!=5этот цикл может не нарушиться. Это безопаснее проверить на неравенство.

Редактировать о читаемости. Форма неравенства используется гораздо чаще. Таким образом, это очень быстро для чтения, так как нет ничего особенного для понимания (нагрузка на мозг снижается, потому что задача обычная). Так что читателям здорово использовать эти привычки.


6
Этот тип внутреннего «if (условие) затем i ++» - первое, о чем я подумал.
WernerCD

3
Ожидал, что это будет самый высоко оцененный ответ; Я был удивлен, что мне пришлось прокрутить это далеко, чтобы найти его. Это намного больше убеждает начинающего программиста, чем «ну, вы должны использовать самое сильное условие». Другие ответы, если они остаются на вершине, должны включать это как ясное и очевидное объяснение того, что может пойти не так с предложенным кодом OP.
MSL

1
@msouth Я полностью согласен, но мой POV довольно субъективен;)
johan d

3
@msouth Мне нравится, когда неожиданное производственное поведение раскрывает мои ошибки, не так ли? :-)
deworde

3
@msouth Послушай, все, что нам нужно сделать, это никогда не совершать ошибок, и все будет хорошо. Simples.
deworde

48

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

Единственный случай, когда защитное программирование не требуется, - это когда состояния были доказаны до и после условий (но доказательство того, что это самое защитное из всех программ).


2
Один хороший пример в поддержку этого: Память уязвима к скачкам битов ( SEU ), вызванным излучением. При использовании intдля i != 15условия счетчик будет работать в течение длительного времени, если что-то выше четвертого бита будет перевернуто, особенно на машинах с sizeof(int)64. SEU представляют собой очень реальную проблему на большой высоте или в космосе (из-за более высокой радиации) или в больших суперкомпьютерах (потому что присутствует так много памяти).
Джош Сэнфорд

2
Думать, что ваша программа будет работать нормально, когда радиация меняет вашу память, - это оптимистичный и антикатастрофический способ мышления ... хороший комментарий! :)
Луис Колорадо

37

Я бы сказал, что такое выражение, как

for ( int i = 0 ; i < 100 ; ++i )
{
  ...
}

более выразительно намерения, чем

for ( int i = 0 ; i != 100 ; ++i )
{
  ...
}

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


13
«Я хочу, чтобы этот цикл выполнялся не более 5 раз» против «Я хочу запускать этот цикл столько раз, сколько необходимо, за исключением ровно 5 раз, чего я не хочу».
епископ

+1 за упоминание верхней границы диапазона. Я думаю, что это важно для числовых диапазонов. ! = не определяет правильный диапазон в цикле for, как эти
element11

22

Итераторы - важный случай, когда вы чаще всего используете !=нотацию:

for(auto it = vector.begin(); it != vector.end(); ++it) {
 // do stuff
}

Конечно, на практике я бы написал то же самое, полагаясь на range-for:

for(auto & item : vector) {
 // do stuff
}

но суть остается: обычно сравнивают итераторы, используя ==или !=.


2
Итераторы часто имеют только понятие равенства / неравенства, а не упорядочения, поэтому вы используете !=их. Например, в a std::vectorвы можете использовать, <поскольку итератор привязан к индексу / указателю, но в a std::setтакого строгого упорядочения нет, так как он проходит по двоичному дереву.
Мартин С.

19

Условие цикла является инвариантом принудительного цикла.

Предположим, вы не смотрите на тело цикла:

for (int i = 0; i != 5; ++i)
{
  // ?
}

в этом случае вы знаете, в начале итерации цикла, что iне равно 5.

for (int i = 0; i < 5; ++i)
{
  // ?
}

в этом случае вы знаете, в начале итерации цикла, что iменьше, чем 5.

Второе намного, намного больше информации, чем первое, нет? Теперь намерение программиста (почти наверняка) то же самое, но если вы ищете ошибки, уверенность в чтении строки кода - это хорошо. А второй исполняет этот инвариант, что означает, что некоторые ошибки, которые могут укусить вас в первом случае, просто не могут произойти (или, скажем, не вызвать повреждение памяти) во втором случае.

Вы знаете больше о состоянии программы, читая меньше кода, <чем!= . И на современных процессорах они занимают столько же времени, сколько и без разницы.

Если вашим iтелом не манипулировали, и оно всегда увеличивалось на 1, и оно начиналось меньше, чем5 , разницы не было бы. Но чтобы узнать, манипулировали ли вы этим, вам необходимо подтвердить каждый из этих фактов.

Некоторые из этих фактов относительно просты, но вы можете ошибиться. Однако проверка всего тела цикла - это боль.

В C ++ вы можете написать такой indexesтип, что:

for( const int i : indexes(0, 5) )
{
  // ?
}

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

Чем больше информации вы можете извлечь из строки кода без понимания контекста, тем легче будет отследить, что происходит не так. <в случае целочисленных циклов дает вам больше информации о состоянии кода в этой строке, чем !=делает.



13

Может случиться, что для переменной iзадано какое-то большое значение, и если вы просто используете !=оператор, вы попадете в бесконечный цикл.


1
Не совсем бесконечный - на большинстве реализаций C, i будет тихо обтекать, когда он максимален. Но да, скорее всего, дольше, чем вы готовы ждать.
usr2564301

12

Как видно из других многочисленных ответов, есть причины использовать <вместо! =, Что поможет в крайних случаях, начальных условиях, непреднамеренной модификации счетчика цикла и т. Д. ...

Честно говоря, я не думаю, что вы можете подчеркнуть важность конвенции. Для этого примера другим программистам будет достаточно легко увидеть, что вы пытаетесь сделать, но это приведет к двойному результату. Одна из задач при программировании - сделать его максимально читаемым и привычным для всех, поэтому неизбежно, когда кому-то приходится обновлять / изменять ваш код, не требуется много усилий, чтобы выяснить, что вы делали в разных блоках кода. , Если бы я видел, как кто-то использовал !=, я бы предположил, что была причина, по которой они использовали это вместо, <и если бы это был большой цикл, я бы просмотрел все это, пытаясь выяснить, что вы сделали, что сделало это необходимым ... и это потраченное время.


12

Как уже сказал Ян Ньюсон, вы не можете надежно перебрать плавающую переменную и выйти с помощью !=. Например,

for (double x=0; x!=1; x+=0.1) {}

будет фактически зацикливаться вечно, потому что 0.1 не может быть точно представлен в плавающей запятой, следовательно, счетчик едва ли пропустит 1. С <этим он заканчивается.

(Тем не менее, обратите внимание, что это в основном неопределенное поведение, независимо от того, получаете ли вы 0,9999 ... в качестве последнего принятого числа - что нарушает предположение меньше - или уже выходите на отметке 1,0000000000000001.)


10

Я использую прилагательное «технический», чтобы обозначить поведение / причуды языка и побочные эффекты компилятора, такие как производительность сгенерированного кода.

Для этого ответ: нет (*). (*) «Пожалуйста, обратитесь к руководству по вашему процессору». Если вы работаете с какой-либо периферийной системой RISC или FPGA, вам может потребоваться проверить, какие инструкции генерируются и сколько они стоят. Но если вы используете довольно много любую обычную современную архитектуру, то нет существенной разницы уровня процессора в стоимости между lt, eq, neи gt.

Если вы используете крайний случай вы можете обнаружить , что !=требуется три операции ( cmp, not, beq) против двух ( cmp, blt xtr myo). Опять же, RTM в этом случае.

По большей части, причины - это защита / укрепление, особенно при работе с указателями или сложными циклами. Рассматривать

// highly contrived example
size_t count_chars(char c, const char* str, size_t len) {
    size_t count = 0;
    bool quoted = false;
    const char* p = str;
    while (p != str + len) {
        if (*p == '"') {
            quote = !quote;
            ++p;
        }
        if (*(p++) == c && !quoted)
            ++count;
    }
    return count;
}

Менее надуманным примером будет то, где вы используете возвращаемые значения для выполнения приращений, принимая данные от пользователя:

#include <iostream>
int main() {
    size_t len = 5, step;
    for (size_t i = 0; i != len; ) {
        std::cout << "i = " << i << ", step? " << std::flush;

        std::cin >> step;
        i += step; // here for emphasis, it could go in the for(;;)
    }
}

Попробуйте это и введите значения 1, 2, 10, 999.

Вы могли бы предотвратить это:

#include <iostream>
int main() {
    size_t len = 5, step;
    for (size_t i = 0; i != len; ) {
        std::cout << "i = " << i << ", step? " << std::flush;
        std::cin >> step;
        if (step + i > len)
            std::cout << "too much.\n";
        else
            i += step;
    }
}

Но то, что вы, вероятно, хотели

#include <iostream>
int main() {
    size_t len = 5, step;
    for (size_t i = 0; i < len; ) {
        std::cout << "i = " << i << ", step? " << std::flush;
        std::cin >> step;
        i += step;
    }
}

Существует также некоторая склонность к соглашению <, поскольку упорядочение в стандартных контейнерах часто основано operator<, например, хеширование в нескольких контейнерах STL определяет равенство, говоря

if (lhs < rhs) // T.operator <
    lessthan
else if (rhs < lhs) // T.operator < again
    greaterthan
else
    equal

Если lhsи rhsпользовательский класс, пишущий этот код как

if (lhs < rhs) // requires T.operator<
    lessthan
else if (lhs > rhs) // requires T.operator>
    greaterthan
else
    equal

Разработчик должен предоставить две функции сравнения. Так <стал любимым оператором.


Для удобства чтения i + = шаг должен быть в части управления. В случае, если вы шагните = 0
Миккель Кристиансен

1
Хотя это поможет улучшить читаемость хорошего кода, я хотел бы подчеркнуть недостаток в ошибочной версии
kfsone

9

Существует несколько способов написания любого вида кода (обычно), просто в этом случае бывает два способа (три, если вы считаете <= и> =).

В этом случае люди предпочитают> и <, чтобы убедиться, что даже если в цикле произойдет что-то непредвиденное (например, ошибка), оно не будет бесконечно (BAD). Рассмотрим следующий код, например.

for (int i = 1; i != 3; i++) {
    //More Code
    i = 5; //OOPS! MISTAKE!
    //More Code
}

Если бы мы использовали (i <3), мы были бы в безопасности от бесконечного цикла, потому что он накладывал большее ограничение.

Это действительно ваш выбор, хотите ли вы, чтобы ошибка в вашей программе закрыла все это или продолжала работать с этой ошибкой.

Надеюсь, это помогло!


Можно утверждать, что бесконечный цикл был бы хорошим индикатором ошибки, но другой мог бы утверждать, что i = 5 не обязательно является ошибкой
njzk2

7

Наиболее распространенная причина использования < является соглашение. Все больше программистов считают такие циклы «пока индекс находится в диапазоне», а не «пока индекс не достигнет конца». Там, где это возможно, стоит придерживаться соглашения.

С другой стороны, многие ответы здесь утверждают, что использование <формы помогает избежать ошибок. Я бы сказал, что во многих случаях это просто помогает скрывать ошибки. Если предполагается, что индекс цикла достигает конечного значения, и вместо этого он действительно выходит за его пределы, то происходит нечто, чего вы не ожидали, что может привести к неисправности (или быть побочным эффектом другой ошибки). <, Вероятно , задержит открытие ошибки. Это !=более вероятно приведет к остановке, зависанию или даже падению, что поможет вам быстрее обнаружить ошибку. Чем раньше обнаружена ошибка, тем дешевле ее исправить.

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

Например, если вы шагаете по простой строке C, обычно пишется:

for (char *p = foo; *p != '\0'; ++p) {
  // do something with *p
}

чем

int length = strlen(foo);
for (int i = 0; i < length; ++i) {
  // do something with foo[i]
}

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

В случае с C ++ std :: string вы будете использовать цикл for на основе диапазона, стандартный алгоритм или итераторы, даже если длина легко доступна. Если вы используете итераторы, соглашение заключается в использовании!= а не <, как в:

for (auto it = foo.begin(); it != foo.end(); ++it) { ... }

Точно так же итерация дерева или списка или deque обычно вовлекает наблюдение за нулевым указателем или другим стражем, а не проверяет, остается ли индекс в пределах диапазона.


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

7

Одна из причин не использовать эту конструкцию - числа с плавающей запятой. !=Это очень опасное сравнение с поплавками, так как оно редко оценивается как истинное, даже если числа выглядят одинаково. <или >устраняет этот риск.


Если ваш цикл итератора является числом с плавающей точкой, то по !=сравнению с <наименее ваших проблем.
Лундин

7

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

(1) немного избыточности. На естественном языке мы обычно предоставляем больше информации, чем это строго необходимо, очень похоже на код с исправлением ошибок. Здесь дополнительная информация заключается в том, что переменная цикла i(см., Как я использовал избыточность здесь? Если вы не знали, что означает «переменная цикла», или если вы забыли имя переменной, после прочтения «переменной цикла»i » у вас есть полная информация) меньше 5 во время цикла, а не просто отличается от 5. Избыточность повышает удобочитаемость.

(2) Конвенция. Языки имеют конкретные стандартные способы выражения определенных ситуаций. Если вы не будете следовать установленному способу сказать что-то, вас все равно поймут, но усилия для получателя вашего сообщения будут больше, потому что определенные оптимизации не будут работать. Пример:

Не говорите о горячем пюре. Просто осветить трудности!

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

Не бейте вокруг куста. Просто объясни проблему!

Это верно даже в том случае, если синонимы, использованные в первой версии, соответствуют ситуации лучше, чем обычные слова в английской идиоме. Подобные силы действуют, когда программисты читают код. Это также почему 5 != iи 5 > iстранные способы выразить это, если вы не работаете в среде, в которой это стандартно, чтобы поменять местами более нормальный i != 5и i < 5таким образом. Такие диалектные сообщества существуют, вероятно, потому, что последовательность помогает легче запомнить, 5 == iа не естественную, но подверженную ошибкам i == 5.


1
Ausgezeichnet . Точно так же нельзя написать тест как i < 17+(36/-3)(даже если эти константы используются в другом месте; тогда вы должны выписать их имена для ясности!).
usr2564301

6

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

Я бы сказал, что следует по возможности использовать сравнения равенства вместо реляционных сравнений, поскольку сравнения равенства накладывают меньше требований на сравниваемые значения. Быть EqualityComparable - меньшее требование, чем LessThanComparable .

Другим примером, который демонстрирует более широкую применимость сравнения равенства в таких контекстах, является популярная головоломка с реализацией unsignedитерации вплоть до 0. Это можно сделать как

for (unsigned i = 42; i != -1; --i)
  ...

Обратите внимание, что вышесказанное в равной степени применимо как к знаковой, так и к беззнаковой итерации, тогда как реляционная версия разбивается на неподписанные типы


2

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

  • Отрицания затрудняют понимание кода
  • <или >только один символ, но !=два

4
Вторая пуля - в лучшем случае легкомысленная причина. Код должен быть правильным и понятным. Компакт гораздо менее важен.
Джонатан Леффлер

@JonathanLeffler Хорошо, в REPL TDD этот дополнительный символ - одна лишняя наносекунда для синтаксического анализа и раз в миллиард итераций, чтобы найти ошибку, вызванную записью 5 != $i, которая заняла целую секунду моей жизни. Э-э ... хихиканье
епископ

1

В дополнение к различным людям, которые упомянули, что это снижает риск, оно также уменьшает количество перегрузок функций, необходимых для взаимодействия с различными стандартными компонентами библиотеки. Например, если вы хотите, чтобы ваш тип был сохранен в std::set, или использовался в качестве ключа std::map, или использовался с некоторыми из алгоритмов поиска и сортировки, стандартная библиотека обычно использует std::lessдля сравнения объектов, поскольку большинству алгоритмов нужен только строгий слабый порядок , Таким образом, становится хорошей привычкой использовать <сравнения вместо !=сравнений (где это, конечно, имеет смысл).


1
Вы можете быть правы по количеству перегрузок, но при рассмотрении требований к объектам можно определить !=оператор практически для любого объекта , но не всегда возможно определить строгое слабое упорядочение. В этом смысле !=было бы лучше, потому что он предъявляет меньше требований к типу. Тем не менее, я все еще остаюсь с <.
idclev 463035818

0

С точки зрения синтаксиса проблем нет, но логика, лежащая в основе этого выражения, 5!=iневерна.

По моему мнению, использование !=для установки границ цикла for не логично, потому что цикл for либо увеличивает, либо уменьшает индекс итерации, поэтому установка цикла для итерации до тех пор, пока индекс итерации не выйдет за пределы (!= к чему-либо), не является правильная реализация.

Она будет работать, но он склонен к проступку , так как обработка данных границы теряется при использовании !=для дополнительных проблем ( это означает , что вы знаете , с самого начала , если приращения или уменьшается), поэтому вместо используются.!=<>>==>


2
Логика совершенно в порядке. Когда iэто 5цикл завершается. Вы хотели сказать что-то еще?
Дэн Гетц,

1
Если данные не передаются должным образом из-за высокой температуры, электромагнитные воздействия влияют на ваш код вечно. Если вы делаете это на обычной рабочей станции или сервере с оперативной памятью ECC, то это не проблема (ни логическая, ни техническая, ни физическая)
Маркус
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.