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

В C (или C ++ в этом отношении) указатели являются особыми, если они имеют нулевое значение: я советую устанавливать указатели в ноль после освобождения их памяти, потому что это означает, что освобождение указателя снова не опасно; когда я вызываю malloc, он возвращает указатель со значением ноль, если он не может получить мне память; Я все время использую if (p != 0), чтобы убедиться, что переданные указатели действительны и т. Д.

Но поскольку адресация памяти начинается с 0, разве 0 не является таким же допустимым адресом, как любой другой? Как можно использовать 0 для обработки нулевых указателей, если это так? Почему отрицательное число не равно нулю?


Edit:

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

  • Как и все остальное в программировании, это абстракция. Просто константа, не связанная с адресом 0. C ++ 0x подчеркивает это, добавляя ключевое слово nullptr.

  • Это даже не абстракция адреса, это константа, указанная в стандарте C, и компилятор может преобразовать ее в какое-то другое число при условии, что он никогда не будет равен "реальному" адресу и равен другому нулю указатели, если 0 - не лучшее значение для платформы.

  • Если это не абстракция, как это было раньше, адрес 0 используется системой и недоступен программисту.

  • Я признаю, что мое отрицательное числовое предложение было немного диким мозговым штурмом. Использование целого числа со знаком для адресов немного расточительно, если оно означает, что кроме нулевого указателя (-1 или чего-либо еще) пространство значений равномерно разделено между положительными целыми числами, которые дают действительные адреса, и отрицательными числами, которые просто напрасны.

  • Если какое-либо число всегда представляется типом данных, то оно равно 0. (Вероятно, 1 тоже. Я думаю об одномразрядном целом числе, которое будет 0 или 1, если оно не подписано, или просто бит со знаком, если оно подписано, или двухбитовое целое число, которое будет [-2, 1]. Но тогда вы можете просто перейти к 0, равному нулю, и 1, являющемуся единственным доступным байтом в памяти.)

Тем не менее, в моем разуме есть что-то нерешенное. Вопрос переполнения стека Указатель на определенный фиксированный адрес говорит мне, что даже если 0 для нулевого указателя является абстракция, другие значения указателя не обязательно. Это заставляет меня опубликовать еще один вопрос о переполнении стека, Могу ли я когда-нибудь получить доступ к нулевому адресу? .

113 голосов | спросил Joel 3 Maypm10 2010, 21:16:56

20 ответов


0

2 балла:

  • только константное значение 0 в исходном коде является нулевым указателем - реализация компилятора может использовать любое значение, которое оно хочет или нуждается в работающем коде. Некоторые платформы имеют специальное значение указателя, которое «недопустимо», и реализация может использовать его как нулевой указатель. У часто задаваемых вопросов C есть вопрос, «Серьезно, есть ли на реальных машинах действительно ненулевые нулевые указатели или другие представления? для указателей на разные типы? ", это указывает на несколько платформ, которые использовали это свойство 0, являющееся нулевым указателем в источнике C, в то время как по-разному представлены во время выполнения. В стандарте C ++ есть примечание, в котором четко указывается, что преобразование «выражения с целочисленной константой со значением ноль всегда приводит к нулевому указателю, но преобразование других выражений с нулевым значением не обязательно приводит к нулевому указателю».

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

Единственные требования для нулевого указателя:

  • гарантированно сравнивается неравное с указателем на реальный объект
  • любые два нулевых указателя будут сравниваться одинаково (C ++ уточняет это так, что это нужно только для указателей одного и того же типа)
ответил Michael Burr 3 Maypm10 2010, 21:22:30
0

Исторически адресное пространство, начинающееся с 0, всегда было ПЗУ, использовалось для некоторой операционной системы или подпрограмм обработки прерываний низкого уровня, в настоящее время, поскольку все является виртуальным (включая адресное пространство), операционная система может сопоставить любое распределение любому адресу так что он может специально НЕ размещать что-либо по адресу 0.

ответил Aviad P. 3 Maypm10 2010, 21:22:24
0

IIRC, значение "нулевого указателя" не гарантируется равным нулю. Компилятор переводит 0 в любое «нулевое» значение, подходящее для системы (которое на практике, вероятно, всегда равно нулю, но не обязательно). Тот же перевод применяется всякий раз, когда вы сравниваете указатель с нулем. Поскольку вы можете сравнивать только указатели друг с другом и с этим специальным значением-0, это изолирует программиста от знания чего-либо о представлении памяти в системе. Что касается того, почему они выбрали 0 вместо 42 или что-то подобное, я собираюсь догадаться, что это потому, что большинство программистов начинают считать с 0 :) (Кроме того, в большинстве систем 0 - это первый адрес памяти, и они хотели, чтобы он был удобным, так как в практические переводы, которые я описываю, на самом деле происходят редко; язык их просто учитывает).

ответил rmeador 3 Maypm10 2010, 21:21:53
0

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

Ни в C, ни в C ++ указатели не могут "иметь нулевое значение". Указатели не являются арифметическими объектами. Они не могут иметь числовые значения, такие как «ноль» или «отрицательный» или что-либо в этом роде Так что ваше утверждение о «указателях ... имеют нулевое значение» просто не имеет смысла.

В C & Указатели C ++ могут иметь зарезервированное значение нулевого указателя . Фактическое представление значения нулевого указателя не имеет ничего общего с любыми "нулями". Это может быть абсолютно все, что подходит для данной платформы. Это правда, что на большинстве платформ значение нулевого указателя физически представлено фактическим нулевым значением адреса. Однако, если на какой-либо платформе адрес 0 фактически используется для какой-либо цели (то есть вам может потребоваться создать объекты по адресу 0), значение нулевого указателя на такой платформе, скорее всего, будет другим. Физически он может быть представлен как 0xFFFFFFFF адресное значение или как 0xBAADBAAD значение адреса, например.

Тем не менее, независимо от того, как значение нулевого указателя представлено на данной платформе, в вашем коде вы все равно продолжите обозначать нулевые указатели константой 0. Чтобы присвоить значение нулевого указателя данному указателю, вы будете продолжать использовать выражения типа p = 0. Обязанность компилятора - реализовать то, что вы хотите, и преобразовать его в правильное представление значения нулевого указателя, т.е. преобразовать его в код, который поместит значение адреса в 0xFFFFFFFF в указатель, например, p.

Короче говоря, тот факт, что вы используете 0 в своем коде Sorce для генерации значений нулевого указателя, не означает, что нулевой Значение указателя каким-то образом привязано к адресу 0. 0, который вы используете в своем исходном коде, является просто «синтаксическим сахаром», который не имеет абсолютно никакого отношения к фактическому физическому адресу нулевого указателя значение «указывает» на.

ответил AnT 3 Maypm10 2010, 22:12:19
0
  

Но поскольку адресация памяти начинается с 0, не является ли 0 таким же допустимым адресом, как и любой другой?

В некоторых /многих /всех операционных системах адрес памяти 0 в некотором роде особенный. Например, он часто сопоставляется с недействительной /несуществующей памятью, что вызывает исключение, если вы пытаетесь получить к нему доступ.

  

Почему вместо этого отрицательное число не равно нулю?

Я думаю, что значения указателей обычно обрабатываются как числа без знака: в противном случае, например, 32-разрядный указатель сможет адресовать только 2 ГБ памяти вместо 4 ГБ.

ответил ChrisW 3 Maypm10 2010, 21:25:12
0

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

На Commodore Pet, Vic20 и C64, которые были первыми машинами, на которых я работал, ОЗУ начиналось с местоположения 0, поэтому было вполне допустимо читать и писать, используя нулевой указатель, если вы действительно этого хотите.

ответил KPexEA 4 Mayam10 2010, 01:09:25
0

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

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

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

ответил Axel Gneiting 3 Maypm10 2010, 21:22:44
0

Хотя C использует 0 для представления нулевого указателя, имейте в виду, что значение самого указателя не может быть нулем. Однако большинство программистов используют только те системы, в которых нулевой указатель фактически равен 0.

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

ответил George Phillips 3 Maypm10 2010, 21:25:41
0

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

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

ответил Fred Haslam 3 Maypm10 2010, 21:25:05
0

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

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


...
assert(ptr && "You're deleting this pointer twice, look for a bug?");
delete ptr;
ptr = 0;
...

Это требует некоторой дополнительной типизации и одной дополнительной проверки во время отладочных сборок, но она обязательно даст вам то, что вы хотите: обратите внимание, когда ptr удаляется «дважды». Альтернатива, представленная в обсуждении комментария, не устанавливает указатель на ноль, чтобы вы могли получить сбой, просто не гарантированно будет успешным. Хуже того, в отличие от вышеизложенного, он может вызвать сбой (или намного хуже!) У пользователя, если одна из этих «ошибок» попадет на полку. Наконец, эта версия позволяет продолжить запуск программы, чтобы увидеть, что на самом деле происходит.

Я понимаю, что это не отвечает на заданный вопрос, но я волновался, что кто-то, читающий комментарии, может прийти к выводу, что считается «хорошей практикой» - НЕ устанавливать указатели на 0, если возможно, что их отправляют бесплатно () или удалите дважды. В тех немногих случаях, когда это возможно, НИКОГДА не рекомендуется использовать Undefined Behavior в качестве инструмента отладки. Никто, кому когда-либо приходилось выслеживать ошибку, которая в конечном итоге была вызвана удалением неверного указателя, не предложил бы этого. Подобные ошибки занимают часы, чтобы выследить и почти всегда воздействовать на программу совершенно неожиданным способом, который трудно или невозможно отследить до исходной проблемы.

ответил Crazy Eddie 4 Maypm10 2010, 20:46:43
0

Важная причина, по которой многие операционные системы используют нулевые биты для представления нулевого указателя, заключается в том, что это означает memset(struct_with_pointers, 0, sizeof struct_with_pointers) и тому подобное. установит все указатели внутри struct_with_pointers на нулевые указатели. Это не гарантируется стандартом C, но многие, многие программы предполагают это.

ответил zwol 8 32017vEurope/Moscow11bEurope/MoscowWed, 08 Nov 2017 23:51:46 +0300 2017, 23:51:46
0

На одной из старых машин DEC (я думаю, PDP-8) среда выполнения C защищала бы память первой страницы памяти, так что любая попытка доступа к памяти в этом блоке вызывала бы исключение.

ответил Paul Tomblin 3 Maypm10 2010, 21:23:19
0

Выбор значения Sentinel является произвольным, и на самом деле это решается в следующей версии C ++ (неофициально известной как «C ++ 0x», скорее всего, в будущем известной как ISO C ++ 2011) с введение ключевого слова nullptr для представления нулевого указателя. В C ++ значение 0 может использоваться в качестве инициализирующего выражения для любого POD и для любого объекта с конструктором по умолчанию, и оно имеет особое значение назначения значения часового в случае инициализации указателя. Что касается того, почему не было выбрано отрицательное значение, адреса обычно варьируются от 0 до 2 N -1 для некоторого значения N. Другими словами, адреса обычно обрабатываются как значения без знака. Если бы максимальное значение использовалось в качестве значения часового, то оно должно было бы меняться от системы к системе в зависимости от объема памяти, тогда как 0 всегда является представимым адресом. Он также используется по историческим причинам, так как адрес памяти 0 обычно не использовался в программах, и в настоящее время большинство ОС имеют части ядра, загруженные в нижнюю страницу (и) памяти, и такие страницы обычно защищены таким образом, что если касание (разыменование) программой (сохранение ядра) вызовет ошибку.

ответил Michael Aaron Safyan 3 Maypm10 2010, 21:51:48
0

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

ответил JustJeff 4 Mayam10 2010, 02:55:24
0

В редких случаях ОС позволяет вам писать по адресу 0. Распространено то, что связано с ОС, в нижней памяти; а именно, IDT, таблицы страниц и т. д. (Таблицы должны быть в ОЗУ, и их проще прикрепить внизу, чем пытаться определить, где находится верх ОЗУ.) И ни одна ОС в здравом уме не позволит вам редактировать системные таблицы волей-неволей.

Возможно, это не было умы K & R, когда они сделали C, но это (наряду с тем фактом, что 0 == null довольно легко запомнить) делает 0 популярным выбором.

ответил cHao 3 Maypm10 2010, 21:30:08
0

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

  • В C ++ при инициализации нуля указатель устанавливает его в ноль.
  • На многих процессорах более эффективно установить значение 0 или проверить его на значение, равное /не равное 0, чем для любой другой константы.
ответил Mark Ransom 15 J000000Monday13 2013, 08:12:04
0

Это зависит от реализации указателей в C /C ++. Нет конкретной причины, по которой NULL эквивалентен в присваиваниях указателю.

ответил Nagabhushan Baddi 18 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowFri, 18 Sep 2015 17:30:32 +0300 2015, 17:30:32
0

Для этого есть исторические причины, но есть и причины для оптимизации.

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

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

Недорогие сравнения для большинства процессоров: подпись меньше 0 и равно 0. (подпись больше 0 и не равно 0 подразумевается обоими из них)

Так как 1 значение из всех возможных значений должно быть зарезервировано как плохое или неинициализированное, вы также можете сделать его тем, которое имеет самый дешевый тест на эквивалентность плохому значению. Это также верно для символьных строк, оканчивающихся на \ 0.

Если бы вы попытались использовать для этой цели больше или меньше 0, то в итоге вы бы разбили свой диапазон адресов пополам.

ответил nategoose 4 Mayam10 2010, 00:49:41
0

Константа 0 используется вместо NULL поскольку C был создан несколькими пещерными людьми триллионы лет назад, NULL, NIL, ZIP или NADDA было бы гораздо лучше, чем 0.

  

Но поскольку адресация памяти начинается с   0, не 0 так же, как действительный адрес, как   любой другой?

Действительно. Хотя многие операционные системы не позволяют отображать что-либо с нулевого адреса, даже в виртуальном адресном пространстве (люди понимали, что C является небезопасным языком, и, отражая, что ошибки разыменования нулевого указателя очень распространены, решили «исправить» их, запретив код пользовательского пространства для сопоставления со страницей 0. Таким образом, если вы вызываете функцию обратного вызова, но указатель обратного вызова равен NULL, вы в конечном итоге не выполните какой-то произвольный код).

  

Как можно использовать 0 для обработки нуля   указатели, если это так?

Поскольку 0, используемое по сравнению с указателем, будет заменено некоторым значением специфичной для реализации , которое возвращаемое значение malloc при ошибке malloc.

  

Почему отрицательное число не равно нулю   вместо этого?

Это было бы еще более запутанным.

ответил L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳ 4 Mayam10 2010, 02:44:17
0

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

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

Ответ

Вот несколько других причин, которые могут быть основными факторами для NULL == 0

  1. Тот факт, что ноль является ложным, так что можно сделать непосредственно if(!my_ptr) вместо if(my_ptr==NULL)
  2. Тот факт, что неинициированные глобальные целые по умолчанию инициализируются всеми нулями, и как таковой указатель всех нулей будет считаться неинициализированным.

Здесь я хотел бы сказать слово о других ответах

Не из-за синтаксического сахара

Сказать, что NULL равен нулю из-за синтаксического сахара, не имеет особого смысла, если так, почему бы не использовать индекс 0 массива для хранения его длины?

На самом деле C - это язык, который больше всего напоминает внутреннюю реализацию, имеет ли смысл говорить, что C выбрал ноль только из-за синтаксического сахара? Они скорее предоставят ключевое слово null (как это делают многие другие языки), чем сопоставят ноль с NULL!

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

1) Спецификация

Тем не менее, хотя это правда, что спецификация C говорит от константы 0 как нулевой указатель (раздел 6.3.2.3), а также определяет NULL для определения реализацией (раздел 7.19 в спецификации C11 и 7.17 в спецификации C99 ), факт остается фактом, что в книге «Язык программирования C», написанной изобретателями языка C, в разделе 5.4 указано следующее:

  

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

     

Указатель и целые числа не являются взаимозаменяемыми, единственное исключение - ноль: постоянный ноль может быть назначен указателю, а указатель может сравниваться с постоянным нулем. Символическая константа NULL часто используется вместо нуля, как мнемоника, чтобы более четко указать, что это специальное значение для указателя. NULL определяется в. Мы будем использовать NULL впредь.

Как можно видеть (из слов «нулевой адрес»), по крайней мере, первоначальное намерение авторов C было с нулевым адресом, а не с постоянным нулем, более того, как следует из этого отрывка, причина, почему спецификация говорит от константы ноль, вероятно, не для того, чтобы исключить выражение, которое оценивается в ноль, но вместо этого включить целочисленную константу ноль, чтобы быть единственной целочисленной константой, разрешенной для использования в контексте указателя без приведения.

2) Сводка

Хотя в спецификации не указано явно, что нулевой адрес может обрабатываться иначе, чем нулевая константа, в нем не говорится, что это не так, и тот факт, что при работе с константой нулевого указателя он не претендует на то, что это реализация, определенная так, как это делает константа, определенная NULL , вместо этого заявляя, что она равна нулю, показывает, что между нулевой константой и нулевым адресом может быть разница. >

(Однако, если это так, мне просто интересно, почему NULL определяется реализацией, поскольку в таком случае NULL также может быть постоянным нулем, поскольку компилятор в любом случае должен преобразовать все нулевые константы в фактическую реализацию, определенную NULL?)

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

Кроме того, фактом является то, что современные операционные системы фактически резервируют всю первую страницу (в диапазоне от 0x0000 до 0xFFFF), просто чтобы предотвратить доступ к нулевому адресу из-за пустого указателя C (см. http://en.wikipedia.org/wiki/Zero_page , а также" Windows Via C /C ++ "Джеффри Рихтера и Кристофа Насарре ( опубликовано издательством Microsoft Press) ").

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

Однако, по-видимому, кажется, что авторы C не имели в виду это, и они говорили о «нулевом адресе», и что «C гарантирует, что это никогда не будет действительным адресом», а также «NULL». это просто мнемоника ", ясно показывающая, что его первоначальное намерение не было для" синтаксического сахара ".

Не из-за операционной системы

Также утверждается, что операционная система запрещает доступ к нулевому адресу по нескольким причинам:

1) Когда был написан C, такого ограничения не было, как можно видеть на этой вики-странице http: //en.wikipedia.org/wiki/Zero_page .

2) Дело в том, что компиляторы C обращались к нулевому адресу памяти.

Это похоже на следующий документ BellLabs ( http : //www.cs.bell-labs.com/who/dmr/primevalC.html )

  

Два компилятора отличаются в деталях, как они справляются с этим. В более ранней версии начало можно найти по имени функции; в последующем начало просто принимается равным 0. Это указывает на то, что первый компилятор был написан до того, как у нас была машина с отображением памяти, поэтому источник программы находился не в месте 0, тогда как ко времени второй у нас был PDP-11, который обеспечивал отображение.

(Фактически на сегодняшний день (как я цитировал вышеупомянутые ссылки из википедии и прессы Microsoft) причина ограничения доступа к нулевому адресу - из-за указателей NULL в C! вокруг!)

3) Помните, что C также используется для написания операционных систем и даже компиляторов C!

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

(Аппаратное обеспечение) Объяснение того, как компьютеры (физически) имеют доступ к нулевому адресу

Здесь я хочу объяснить еще один момент: как вообще можно ссылаться на нулевой адрес?

Подумайте об этом на секунду, адреса выбираются процессором, а затем отправляются в виде напряжений на шину памяти, которая затем используется системой памяти для получения фактического адреса, и все же нулевой адрес будет означает отсутствие напряжения, так как физическое оборудование системы памяти обращается к нулевому адресу?

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

ответил L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳ 4 Mayam10 2010, 02:44:17

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132