Когда я впервые начал писать эту статью, у меня была очень большая цель изучить жизнь программы Go: кодирование, компиляция, сборка, компоновка, запуск, выход. Как выполняется каждый шаг, пытаясь понять жизнь программы Go.
В процессе я еще раз просмотрел «Саморазвитие программиста». Это книга о компиляции и линковке, очень подробная и достойная прочтения! Много лет назад, когда я впервые увидел название этой книги, оно мне очень понравилось. Потому что она имитирует книгу, появившуюся в «Короле комедии» Стивена Чоу — «Саморазвитие актера». Стремление к этому!
Прежде чем приступить к этой статье, рекомендую блог крупного хедлайнера - "Программирование, ориентированное на веру". Его серия статей о компиляции Go очень глубока и уходит прямо в исходный код компилятора. Я читал ее много раз. . Ссылку на блог можно получить в Ресурсах.
Идеал очень велик, а реализация очень трудна. Чтобы не разгромить бренд «глубокой расшифровки», на этот раз более скромное имя, хе-хе.
вводить
Мы из одногоHello World
Пример начинается с:
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
Когда я изящно набрал вышеприведенный код hello world на своей вишневой клавиатуре за 1800 долларов,hello.go
Файл представляет собой последовательность байтов, каждый байт представляет символ.
Откройте файл hello.go с помощью vim и в режиме командной строки введите команду:
:%!xxd
Вы можете просмотреть содержимое файла в шестнадцатеричном формате в vim:
Крайний левый столбец представляет значение адреса, средний столбец представляет символ ASCII, соответствующий тексту, а крайний правый столбец — наш код. Выполнить снова в терминалеman ascii
:
По сравнению с таблицей символов ASCII можно обнаружить, что средний столбец и самый правый столбец находятся во взаимно однозначном соответствии. То есть только что написанный файл hello.go полностью представлен символами ASCII, которые называются文本文件
, другие файлы называются二进制文件
.
Конечно, если посмотреть глубже, все данные в компьютере, такие как файлы на диске, данные в сети, на самом деле представляют собой строку битов, в зависимости от того, как вы на это смотрите. В разных контекстах идентичная последовательность байтов может быть представлена как целое число, число с плавающей запятой, строка или машинная инструкция.
Для такого файла, как hello.go, 8 бит, то есть байт, рассматриваются как единица (при условии, что все символы исходной программы представляют собой коды ASCII) и, наконец, интерпретируются как исходный код Go, понятный людям.
Программы Go нельзя запускать напрямую, каждый оператор Go должен быть преобразован в серию инструкций машинного языка низкого уровня, эти инструкции упакованы вместе и сохранены в виде двоичных файлов на диске, то есть исполняемых объектных файлов.
Процесс преобразования из исходных файлов в исполняемые объектные файлы:
Именно система компиляции Go завершает вышеуказанные этапы. Вы должны знать знаменитый GCC (GNU Compile Collection), китайское название — GNU Compiler Suite, он поддерживает C, C++, Java, Python, Objective-C, Ada, Fortran, Pascal и может генерировать машинный код для множества разных машин.
Исполняемые объектные файлы могут выполняться непосредственно на машине. Вообще говоря, сначала выполняется некоторая работа по инициализации, находится запись основной функции и выполняется код, написанный пользователем, после завершения выполнения происходит выход основной функции, а затем выполняются некоторые завершающие работы, и весь процесс завершен.
В следующей статье мы рассмотрим编译
и运行
процесс.
Обзор компиляции и компоновки
Исходный код компилятора в исходном коде Go находится вsrc/cmd/compile
По пути исходный код компоновщика находится вsrc/cmd/link
под дорожкой.
процесс компиляции
Я предпочитаю использовать IDE (интегрированную среду разработки) для написания кода, Goland используется для исходного кода Go, а иногда я напрямую нажимаю кнопку «Выполнить» в строке меню IDE, и программа запускается. На самом деле это подразумевает процесс компиляции и компоновки, и мы обычно комбинируем процесс компиляции и компоновки вместе как сборку.
Процесс компиляции заключается в выполнении лексического анализа, синтаксического анализа, семантического анализа, оптимизации исходного файла и, наконец, генерации файла ассемблерного кода для.s
как расширение файла.
После этого ассемблер превращает ассемблерный код в инструкции, которые машина может выполнять. Поскольку почти каждый ассемблерный оператор соответствует машинной инструкции, это просто взаимно однозначное соответствие, которое относительно простое, без синтаксического и семантического анализа и без оптимизации этих шагов.
Компилятор — это инструмент для перевода языка высокого уровня на машинный язык Процесс компиляции обычно делится на шесть этапов: сканирование, синтаксический анализ, семантический анализ, оптимизация исходного кода, генерация кода и оптимизация объектного кода. Следующая картинка из "Саморазвития программиста":
лексический анализ
Из предыдущих примеров мы знаем, что программный файл Go — это просто набор битов для машины. Мы можем прочитать его, потому что Голанд кодирует эту кучу битов в ASCII (на самом деле UTF-8). Например, 8 бит группируются в группу, соответствующую символу, который можно найти, сравнив кодовую таблицу ASCII.
Когда все двоичные биты отображаются в символы ASCII, мы можем видеть осмысленные строки. Это может быть ключевое слово, например: package, это может быть строка, например: «Hello World».
Лексический анализ на самом деле делает именно это. Входными данными является исходный файл программы Go. В глазах лексического анализатора это просто набор двоичных битов. Он не знает, что это такое. После анализа он становится осмысленной лексемой. Проще говоря, лексический анализ — это процесс преобразования последовательности символов в последовательность токенов в информатике.
Давайте посмотрим на определение, данное в Википедии:
Лексический анализ — это процесс преобразования последовательности символов в последовательность токенов в информатике. Программа или функция, выполняющая лексический анализ, называется лексическим анализатором (лексером), также известным как сканер. Лексический анализатор обычно существует в виде функции, которую должен вызывать синтаксический анализатор.
.go
Файл подается в сканер, который использует有限状态机
Алгоритм делит серию символов исходного кода на серию токенов (Token).
Символы обычно делятся на следующие категории: ключевые слова, идентификаторы, литералы (включая числа, строки), специальные символы (такие как знаки плюс, знаки равенства).
Например, для следующего кода:
slice[i] = i * (2 + 6)
Содержит в общей сложности 16 непустых символов, после сканирования
отметка | тип |
---|---|
slice | идентификатор |
[ | левая квадратная скобка |
i | идентификатор |
] | правая квадратная скобка |
= | назначать |
i | идентификатор |
* | Знак умножения |
( | левая скобка |
2 | номер |
+ | плюс |
6 | номер |
) | правая скобка |
Приведенный выше пример взят из книги "Саморазвитие программиста", которая в основном объясняет содержание, связанное с компиляцией и компоновкой. Это очень увлекательно и рекомендуется для изучения.
Путь в исходном коде Токена, поддерживаемого сканером на языке Go (версия Go в этой статье — 1.9.2):
src/cmd/compile/internal/syntax/token.go
Почувствуй это:
var tokstrings = [...]string{
// source control
_EOF: "EOF",
// names and literals
_Name: "name",
_Literal: "literal",
// operators and operations
_Operator: "op",
_AssignOp: "op=",
_IncOp: "opop",
_Assign: "=",
_Define: ":=",
_Arrow: "<-",
_Star: "*",
// delimitors
_Lparen: "(",
_Lbrack: "[",
_Lbrace: "{",
_Rparen: ")",
_Rbrack: "]",
_Rbrace: "}",
_Comma: ",",
_Semi: ";",
_Colon: ":",
_Dot: ".",
_DotDotDot: "...",
// keywords
_Break: "break",
_Case: "case",
_Chan: "chan",
_Const: "const",
_Continue: "continue",
_Default: "default",
_Defer: "defer",
_Else: "else",
_Fallthrough: "fallthrough",
_For: "for",
_Func: "func",
_Go: "go",
_Goto: "goto",
_If: "if",
_Import: "import",
_Interface: "interface",
_Map: "map",
_Package: "package",
_Range: "range",
_Return: "return",
_Select: "select",
_Struct: "struct",
_Switch: "switch",
_Type: "type",
_Var: "var",
}
Все еще относительно знакомы, включая имена и литералы, операторы, разделители и ключевые слова.
И путь к сканеру такой:
src/cmd/compile/internal/syntax/scanner.go
Наиболее важной функцией является следующая функция, которая непрерывно считывает следующий символ (не следующий байт, потому что язык Go поддерживает кодировку Unicode, в отличие от примера кода ASCII, который мы приводили ранее, символ имеет только один байт), пока эти символы не может сформировать токен.
func (s *scanner) next() {
// ……
redo:
// skip white space
c := s.getr()
for c == ' ' || c == '\t' || c == '\n' && !nlsemi || c == '\r' {
c = s.getr()
}
// token start
s.line, s.col = s.source.line0, s.source.col0
if isLetter(c) || c >= utf8.RuneSelf && s.isIdentRune(c, true) {
s.ident()
return
}
switch c {
// ……
case '\n':
s.lit = "newline"
s.tok = _Semi
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
s.number(c)
// ……
default:
s.tok = 0
s.error(fmt.Sprintf("invalid character %#U", c))
goto redo
return
assignop:
if c == '=' {
s.tok = _AssignOp
return
}
s.ungetr()
s.tok = _Operator
}
Основная логика кода заключается в передачеc := s.getr()
Получите следующий неразобранный символ и пропустите следующие пробелы, возврат каретки, перевод строки, символы табуляции, а затем введите большойswitch-case
оператор, соответствующий различным ситуациям, и, наконец, токен может быть проанализирован, и соответствующие номера строки и столбца записаны, таким образом завершая процесс синтаксического анализа.
Сканер лексического анализатора в текущем пакете предоставляет следующий метод только для верхнего уровня, процесс лексического анализа ленивый, и следующий Токен будет вызван только тогда, когда он понадобится парсеру верхнего уровня.
Разбор
Последовательность токена, генерируемая на предыдущем шаге, должна быть дополнительно обработана для генерации表达式
для узла语法树
.
Как и первый пример,slice[i] = i * (2 + 6)
, результирующее синтаксическое дерево выглядит следующим образом:
Вся инструкция рассматривается как выражение присваивания, левое поддерево — это выражение массива, а правое поддерево — это выражение умножения; выражение массива состоит из двух символьных выражений; выражение умножения состоит из формулы символьного выражения и плюса. -знаковое выражение; выражение со знаком плюс состоит из двух чисел. Символы и числа — это наименьшие выражения, которые уже не могут быть разложены, обычно как конечные узлы дерева.
В процессе синтаксического анализа могут быть обнаружены некоторые формальные ошибки, такие как: если скобки отсутствуют наполовину,+
В числовом выражении отсутствует операнд и т. д.
Синтаксический анализ — это процесс анализа входного текста, состоящего из последовательностей токенов, в соответствии с определенной формальной грамматикой (грамматикой) и определение его грамматической структуры.
Семантический анализ
После завершения синтаксического анализа мы не знаем, каково конкретное значение оператора. как выше*
Если два поддерева числа являются двумя указателями, это недопустимо, но синтаксический анализ не может это обнаружить, а семантический анализ должен это сделать.
Что можно проверить во время компиляции, так это статическую семантику, которую можно рассмотреть на этапе «кода», включая сопоставление типов переменных, преобразование и т. д. Например, при присвоении значения с плавающей запятой переменной-указателю очевидное несоответствие типов приведет к ошибке компиляции. И для ошибок, которые возникают только во время выполнения: случайно, кроме 0, семантический анализ не может его обнаружить.
После завершения фазы семантического анализа каждый узел помечается типом:
На этом этапе компилятор Go проверяет типы констант, типов, объявлений функций и операторов присваивания переменных, а затем проверяет типы ключей в хеше. Функции, реализующие проверку типов, обычно представляют собой гигантские операторы switch/case, состоящие из нескольких тысяч строк.
Проверка типов — это второй этап компиляции языка Go.После лексического и грамматического анализа мы получаем абстрактное синтаксическое дерево, соответствующее каждому файлу.Последующая проверка типов будет проходить по узлам в абстрактном синтаксическом дереве и проверять тип каждого узла.Проверить за грамматические ошибки.
В этом процессе также может быть переписано абстрактное синтаксическое дерево, которое может не только удалить некоторый код, который не будет выполняться, оптимизировать компиляцию и повысить эффективность выполнения, но также изменить тип операции узлов, соответствующих ключевым словам, таким как make и новый.
Например, более часто используемое ключевое слово make может использоваться для создания различных типов, таких как фрагмент, карта, канал и т. д. Когда этот шаг будет достигнут, для ключевого слова make, то есть узла OMAKE, он сначала проверит свой тип параметра и войдет в соответствующую ветвь в соответствии с типом. Если тип параметра — slice, он войдет в ветвь case TSLICE, чтобы проверить, соответствуют ли len и cap требованиям, например len
Генерация промежуточного кода
Мы знаем, что процесс компиляции в целом можно разделить на интерфейсную часть и серверную часть.Фронтенд генерирует независимый от платформы промежуточный код, а серверная часть генерирует разные машинные коды для разных платформ.
Предыдущий лексический анализ, синтаксический анализ, семантический анализ и т. д. относятся к интерфейсу компилятора, а более поздние этапы относятся к серверу компилятора.
В процессе компиляции есть много ссылок на оптимизацию, в этой ссылке имеется в виду оптимизация на уровне исходного кода. Он преобразует синтаксическое дерево в промежуточный код, который является последовательным представлением синтаксического дерева.
Промежуточный код, как правило, не зависит от целевой машины и среды выполнения и имеет несколько распространенных форм: трехадресный код, P-код. Например, самые основные三地址码
Такова, что:
x = y op z
Указывает, что переменная y и переменная z присваиваются x после операции op. op может быть математической операцией, такой как сложение, вычитание, умножение и деление.
Приведенный ранее пример можно записать в следующем виде:
t1 = 2 + 6
t2 = i * t1
slice[i] = t2
Здесь 2 + 6 можно вычислить напрямую, поэтому временную переменную t1 можно «оптимизировать», а переменную t1 можно использовать повторно, поэтому t2 также можно «оптимизировать». После оптимизации:
t1 = i * 8
slice[i] = t1
Промежуточным кодовым представлением языка Go является SSA (Static Single-Assignment), которое называется одиночным присвоением, поскольку каждое имя присваивается только один раз в SSA. .
На этом этапе устанавливаются соответствующие переменные, используемые для генерации промежуточного кода в соответствии с архитектурой ЦП, такие как размер указателей и регистров, используемых компилятором, список доступных регистров и т.д. Генерация промежуточного кода и генерация машинного кода имеют одни и те же настройки.
Некоторые элементы узлов в абстрактном синтаксическом дереве заменяются перед генерацией промежуточного кода. Вот картинка из блога, связанная с принципом компиляции «Программирование, ориентированное на веру»:
Например, операция карты m[i] здесь будет преобразована в mapaccess или mapassign.
Основная программа языка Go будет вызывать функции в среде выполнения при ее выполнении, а это означает, что функции ключевых слов и встроенные функции фактически выполняются компилятором языка и средой выполнения.
Процесс генерации промежуточного кода на самом деле является процессом преобразования из абстрактного синтаксического дерева AST в промежуточный код SSA.В течение этого периода ключевые слова в синтаксическом дереве будут обновлены один раз, а обновленное синтаксическое дерево пройдет несколько циклов обработки. для преобразования конечного SSA Промежуточный код.
Генерация и оптимизация объектного кода
Длина машинного слова, регистры и т. д. разных машин разные, а это означает, что машинный код, работающий на разных машинах, разный. Целью последнего шага является создание кода, который может работать на различных архитектурах ЦП.
Чтобы выжать из машины каждую каплю масла, оптимизатор объектного кода оптимизирует некоторые инструкции, такие как использование инструкций сдвига вместо инструкций умножения.
Эта часть действительно не способна углубляться, но, к счастью, ей и не нужно углубляться. Для инженеров-разработчиков программного обеспечения на прикладном уровне этого достаточно для понимания.
процесс связывания
Процесс компиляции ведется для одного файла, и глобальные переменные или функции, определенные в других модулях, неизбежно ссылаются между файлами, адреса этих переменных или функций могут быть определены только на этом этапе.
Процесс компоновки заключается в компоновке объектных файлов, сгенерированных компилятором, в исполняемые файлы. Конечный файл делится на различные сегменты, такие как сегмент данных, сегмент кода, сегмент BSS и т. д., которые будут загружены в память во время выполнения. Каждый сегмент имеет разные атрибуты чтения-записи и выполнения, что обеспечивает безопасную работу программы.
Для этой части рекомендуется прочитать "Саморазвитие программиста" и "Углубленное понимание компьютерных систем".
Перейти к запуску программы
Все еще используя пример проекта hello-world. Выполнить в корневом каталоге проекта:
go build -gcflags "-N -l" -o hello src/main.go
-gcflags "-N -l"
Это нужно для того, чтобы отключить оптимизацию компилятора и встраивание функций, чтобы предотвратить обнаружение соответствующего местоположения кода при последующей установке точек останова.
Получите исполняемый файл hello, выполните:
[qcrao@qcrao hello-world]$ gdb hello
Войдите в режим отладки gdb, выполнитеinfo files
, чтобы получить заголовок исполняемого файла со списком различных разделов:
При этом мы также получаем адрес входа: 0x450e20.
(gdb) b *0x450e20
Breakpoint 1 at 0x450e20: file /usr/local/go/src/runtime/rt0_linux_amd64.s, line 8.
Это адрес входа программы Go, я работаю в Linux, поэтому файл входаsrc/runtime/rt0_linux_amd64.s
, В каталоге среды выполнения есть различные файлы входа в программу с разными именами, поддерживающие различные операционные системы и архитектуры, код:
TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8
LEAQ 8(SP), SI // argv
MOVQ 0(SP), DI // argc
MOVQ $main(SB), AX
JMP AX
Главное вытащить argc и argv из памяти в регистры. Здесь LEAQ заключается в том, чтобы вычислить адрес памяти, а затем поместить сам адрес памяти в регистр, то есть поместить адрес argv в регистр SI. Наконец, перейдите к:
TEXT main(SB),NOSPLIT,$-8
MOVQ $runtime·rt0_go(SB), AX
JMP AX
продолжать прыгать наruntime·rt0_go(SB)
,Место расположения:/usr/local/go/src/runtime/asm_amd64.s
, код:
TEXT runtime·rt0_go(SB),NOSPLIT,$0
// 省略很多 CPU 相关的特性标志位检查的代码
// 主要是看不懂,^_^
// ………………………………
// 下面是最后调用的一些函数,比较重要
// 初始化执行文件的绝对路径
CALL runtime·args(SB)
// 初始化 CPU 个数和内存页大小
CALL runtime·osinit(SB)
// 初始化命令行参数、环境变量、gc、栈空间、内存管理、所有 P 实例、HASH算法等
CALL runtime·schedinit(SB)
// 要在 main goroutine 上运行的函数
MOVQ $runtime·mainPC(SB), AX // entry
PUSHQ AX
PUSHQ $0 // arg size
// 新建一个 goroutine,该 goroutine 绑定 runtime.main,放在 P 的本地队列,等待调度
CALL runtime·newproc(SB)
POPQ AX
POPQ AX
// 启动M,开始调度goroutine
CALL runtime·mstart(SB)
MOVL $0xf1, 0xf1 // crash
RET
DATA runtime·mainPC+0(SB)/8,$runtime·main(SB)
GLOBL runtime·mainPC(SB),RODATA,$8
Статья в справочнике [Исследуйте процесс запуска программы golang] содержит более глубокое исследование и резюмирует:
- Проверьте ЦП работающей платформы и установите соответствующие флаги, необходимые для запуска программы.
- Инициализация TLS.
- Три метода runtime.args, runtime.osinit и runtime.schedinit выполняют все виды переменных и планировщиков, необходимых для запуска программы.
- runtime.newproc создает новую горутину для привязки к основному методу, написанному пользователем.
- runtime.mstart запускает планирование goroutine.
Наконец, используйте картинку, чтобы подытожить процесс начальной загрузки:
Некоторые важные операции, выполняемые в основной функции, включают: создание нового потока для выполнения функции sysmon, регулярную сборку мусора и планирование вытеснения, запуск gc, выполнение всех функций инициализации и т. д.
Выше приведен процесс запуска, посмотрите на процесс выхода:
Когда основная функция завершит выполнение, она выполнит exit(0) для выхода из процесса. Если процесс не завершается после выполнения exit(0), последний код основной функции всегда будет обращаться к недопустимому адресу:
exit(0)
for {
var x *int32
*x = 0
}
При нормальных обстоятельствах, как только происходит незаконный доступ к адресу, система завершает процесс и использует этот метод, чтобы гарантировать, что процесс завершится.
Объяснение выхода из программы исходит из группового чата "golang runtime reading", который также является высокоуровневой организацией по чтению исходного кода. См. справочные материалы на домашней странице github.
Конечно, эта часть запуска программы Go на самом деле включает в себя создание нового процесса, загрузку исполняемых файлов, передачу управления и другие вопросы. Первые две книги все же рекомендую прочитать, лучше написать не думаю, поэтому описывать не буду.
GoRoot и GoPath
GoRoot — это путь установки Go. mac или unix находится в/usr/local/go
По пути посмотрим, что здесь установлено:
Под каталогом bin:
Под каталогом pkg:
Каталог инструментов Go выглядит следующим образом, наиболее важным из которых является компилятор.compile
, Линкерlink
:
Цель GoPath — предоставить.go
Путь к исходному коду, это концепция рабочей области, и можно установить несколько каталогов. Согласно официальным требованиям Go, GoPath должен содержать три папки:
src
pkg
bin
src хранит исходные файлы, pkg хранит файлы библиотек, скомпилированные из исходных файлов, а суффикс.a
;bin хранит исполняемые файлы.
Детали команды «Перейти»
Выполнить прямо в терминале:
go
Вы можете ознакомиться с командами, связанными с go:
Команды, связанные с компиляцией, в основном следующие:
go build
go install
go run
go build
go build
Используется для компиляции исходных файлов в указанные пакеты и их зависимые пакеты, которые поступят в момент компиляции.$GoPath/src/package
Найдите исходный файл в пути.go build
Вы также можете напрямую скомпилировать указанный файл исходного кода, а также можете указать более одного файла одновременно.
выполнивgo help build
команда, чтобы получитьgo build
Как пользоваться:
usage: go build [-o output] [-i] [build flags] [packages]
-o
Появляется только при компиляции одного пакета, указывает имя выходного исполняемого файла.
-i
Пакеты, от которых зависит цель компиляции, будут установлены.Установка означает создание соответствующих пакетов кода..a
Файл, то есть файл статического библиотеки (будет связан позже), помещается в каталог PKG текущего рабочего пространства, а уровень каталога файла библиотеки совпадает с уровнем исходного кода.
Что касается параметра флагов сборки,build, clean, get, install, list, run, test
Эти команды имеют общий набор:
параметр | эффект |
---|---|
-a | Принудительно перекомпилирует все задействованные пакеты, включая пакеты кода в стандартной библиотеке, что перезаписывает каталог /usr/local/go..a документ |
-n | Процесс выполнения команды печати, на самом деле не выполняется |
-p n | Задает параллельное количество выполняемых команд во время компиляции, n по умолчанию равно количеству ядер ЦП. |
-race | Обнаружение и сообщение о проблемах гонки данных в вашей программе |
-v | Выведите имя пакета кода, участвующего в выполнении команды |
-x | Распечатайте команды, участвующие в процессе выполнения команды, и выполните |
-work | Распечатайте временную папку во время компиляции. Обычно он удаляется после компиляции |
Мы знаем, что файлы исходного кода языка Go делятся на три категории: исходный код команды, исходный код библиотеки и исходный код теста.
Исходный файл команды: запись программы Go, включая
func main()
функция, а в первой строке используетсяpackage main
Декларация относится к основному пакету.
Исходные файлы библиотеки: в основном различные функции, интерфейсы и т. д., например функции инструментов.
Исходный файл теста: начните с
_test.go
Файл с суффиксом для проверки функции и производительности программы.
Уведомление,go build
будет игнорировать*_test.go
документ.
Продемонстрируем на очень простом примереgo build
Заказ. Я создал новый с Голандомhello-world
Проект (отличается от предыдущей программы hello-world тем, что показывает ссылку на пользовательский пакет), структура проекта следующая:
Структуру проекта можно увидеть в крайнем левом углу, включая три папки: bin, pkg, src. Среди них есть main.go в каталоге src, который определяет основную функцию, которая является входом всего проекта, который является упомянутым выше так называемым исходным файлом команд, а также есть каталог util в каталоге src. каталог, который содержит файл util.go.Определяет функцию, которая может получить локальный IP-адрес, который является так называемым исходным файлом библиотеки.
Посередине находится исходный код main.go, который ссылается на два пакета: один — fmt стандартной библиотеки, другой — пакет util, а путь импорта util —util
. Так называемый путь импорта относится к исходному каталогу относительно Go.$GoRoot/src
или$GoPath/src
подпуть под . Например, исходный путь fmt, указанный в основном пакете:/usr/local/go/src/fmt
, а исходный путь утилиты/Users/qcrao/hello-world/src/util
, точно так же, как мы установили GoPath = /Users/qcrao/hello-world.
Крайний справа исходный код библиотечной функции, которая реализует функцию получения нативного IP.
В каталоге src выполнить напрямуюgo build
команда, исполняемый файл создается в каталоге того же уровня, имя файлаsrc
,использовать./src
Команда выполняется напрямую, вывод:
hello world!
Local IP: 192.168.1.3
Мы также можем указать имя сгенерированного исполняемого файла:
go build -o bin/hello
Таким образом, исполняемый файл будет сгенерирован в каталоге bin, и результат работы будет таким же, как и выше.src
Такой же.
На самом деле пакет util можно скомпилировать отдельно. Мы можем выполнить в корневом каталоге проекта:
go build util
Компилятор перейдет по пути $GoPath/src, чтобы найти пакет утилиты (на самом деле папку). также доступен в./src/util
Выполнять прямо в каталогеgo build
компилировать.
Конечно, прямая компиляция исходных файлов библиотеки не приведет к созданию файлов .a, потому что:
Когда команда go build компилирует пакет кода, который содержит только исходные файлы библиотеки (или одновременно компилирует несколько пакетов кода), она выполняет только контрольную компиляцию без вывода каких-либо файлов результатов.
Чтобы показать запущенный процесс всей ссылки компиляции, мы выполняем следующую команду в корневом каталоге проекта:
go build -v -x -work -o bin/hello src/main.go
-v
Напечатает имя скомпилированного пакета,-x
вывести команды, выполненные во время компиляции,-work
Печатать пути к временным файлам, созданным во время компиляции и не удаляемым после завершения компиляции.
Результаты:
Из результатов стрелки на рисунке указывают на то, что этот процесс компиляции включает в себя два пакета: util, командные аргументы. Второй пакет довольно странный, такого имени нет в исходном коде, хорошо? На самом деле этоgo build
Команда обнаруживает, что [пакеты] заполнены.go
файл, создав таким образом фиктивный пакет: command-line-arguments.
При этом compile, ссылка обведены красными прямоугольниками, то есть сначала компилируется пакет util и пакет util.main.go
файлы, соответственно.a
файл, а затем соедините их, чтобы, наконец, создать исполняемый файл, который перемещается в каталог bin и переименовывается в hello.
Также первая строка показывает рабочий каталог во время компиляции, файловая структура этого каталога:
Как видите, уровень каталога hello-world практически такой же. аргументы командной строки — это пакет, в котором находится виртуальный файл main.go. Исполняемые файлы в каталоге exe были перемещены в каталог bin на последнем шаге, поэтому он пуст.
Общий,go build
При выполнении он сначала будет рекурсивно искать пакеты, от которых зависит main.go, а также зависимости зависимостей, до самого нижнего пакета. Это может быть обход в глубину или обход в ширину. Если циклическая зависимость найдена, она завершится напрямую, что также является часто возникающей ошибкой компиляции циклической ссылки.
В нормальных условиях эти зависимости образуют дерево, растущее вверх ногами, корнем которого является файл main.go вверху, а внизу — пакет без каких-либо других зависимостей. Компилятор начнет компиляцию с пакета, представленного крайним левым узлом, один за другим, а после этого скомпилирует пакет предыдущего слоя.
Здесь обратитесь к учебнику по команде go, опубликованному г-ном Хао Линем на github несколько лет назад, и вы можете найти исходный адрес в справочных материалах.
С точки зрения компиляции пакета кода, если пакет кода A зависит от пакета кода B, то пакет кода B называется зависимым пакетом кода пакета кода A (в дальнейшем именуемым зависимым пакетом), а пакет кода A является триггерным кодом. пакет кода пакета B (далее триггерный пакет).
воплощать в жизнь
go build
Если управляемый компьютер имеет несколько логических ядер ЦП, может быть некоторая неопределенность в отношении порядка, в котором компилируются пакеты кода. Однако он должен соответствовать ограничениям: зависимый пакет кода -> текущий пакет кода -> пакет кода триггера.
Кстати, рекомендую браузерный плагин Octotree, при просмотре проекта на гитхабе этот плагин может напрямую отображать файловую структуру всего проекта в браузере, что очень удобно:
На этом этапе вы определенно обнаружите, что каталог pkg в папке hello-wrold, по-видимому, не задействован.
На самом деле в каталоге pkg должны храниться скомпилированные пакеты задействованных библиотечных файлов, то есть некоторые.a
документ. Но во время выполнения сборки эти.a
Файл помещается во временную папку и будет удален сразу после компиляции, поэтому обычно не используется.
Как мы упоминали ранее, добавьте в команду go build-i
Параметры будут устанавливать пакеты, скомпилированные этими библиотечными файлами, то есть эти.a
Файлы будут помещены в каталог pkg.
Выполнить в корневом каталоге проектаgo build -i src/main.go
После этого в каталог pkg был добавлен файл util.a:
darwin_amd64
означает:
ГСНО и ГОАРЧ. Эти две переменные среды не должны быть установлены нами, системные значения по умолчанию.
GOOS — это тип операционной системы, в которой находится Go, а GOARCH — это вычислительная архитектура, в которой находится Go.
На платформе Mac имя этого каталога — darwin_amd64.
После того, как файл util.a сгенерирован, при повторной компиляции файл util.go не будет перекомпилирован, что ускоряет компиляцию.
В то же время в корневом каталоге создается исполняемый файл с именем main, который запускается с именем файла main.go.
Код проекта hello-world выложен на проект github.Go-Questions
, этот проект импортируется вопросом, пытается соединить все точки знаний Го, совершенствуется, с нетерпением ждет своей звезды. Адрес см. в ссылке [Проект Go-Questions hello-world].
go install
go install
Используется для компиляции и установки указанных пакетов кода и их зависимостей. по сравнению сgo build
, он просто добавляет шаг «установки скомпилированного файла результатов в указанный каталог».
Все еще используя предыдущий пример проекта hello-world, мы сначала удаляем каталог pkg и выполняем его в корневом каталоге проекта:
go install src/main.go
或者
go install util
Оба создадут новый в корневом каталогеpkg
каталог и создатьutil.a
документ.
И, когда первое выполняется, исполняемый файл с именем main будет сгенерирован в каталоге GOBIN.
Итак, бегиgo install
команда, соответствующая исходному пакету библиотеки.a
файл будет помещен вpkg
каталог, исполняемый файл, сгенерированный исходным пакетом команды, будет помещен в каталог GOBIN.
go install
Когда GoPath имеет несколько каталогов, могут возникнуть некоторые проблемы.Go 命令教程
, здесь не раскрыто.
go run
go run
Используется для компиляции и запуска исходных файлов команд.
В корневом каталоге проекта hello-world выполните команду go run:
go run -x -work src/main.go
-x может печатать команды, участвующие во всем процессе, -work может видеть временный рабочий каталог:
Как видно из рисунка выше, он по-прежнему сначала компилируется, затем подключается и, наконец, выполняется напрямую, а результат выполнения выводится на печать.
Первая строка выводит рабочий каталог, а полученный исполняемый файл помещается сюда:
main — результирующий исполняемый файл.
Суммировать
На этот раз тема слишком велика и сложная. Из принципа компиляции к процессу GO Startup, к принципу команды GO, каждая тема может быть написана отдельно.
К счастью, есть некоторые очень хорошие книги, а посты в блоге могут ссылаться. Как введение в эту статью, вы можете следить за некоторыми ссылками в рекомендуемом контент для расхождения.
использованная литература
[Полная книга "Самосовершенствование программиста"]book.Douban.com/subject/365…
[Обзор процесса компиляции ориентированного на веру программирования]D Raven ES is.what/go wave-comp…
[чтение во время выполнения golang]GitHub.com/ live ah/ Голаны...
【Проект Go-Questions привет-мир】GitHub.com/езда на велосипеде/go-Q U…
[Заметки об изучении языка го босса знака дождя]github.com/qyuhen/book
[vim в шестнадцатеричном формате]блог woo woo woo.cn on.com/no Principal/ah…
[Перейти к процессу выполнения команды компиляции]halfrost.com/go_command/
[Go] процесс выполнения командыGitHub.com/hyper0small/go_…
[Перейти к лексическому анализу]Смущает, о, о, о, спичка, ты только что GitHub.IO/this-cn/2016/…
[Блог Цао Да golang и ast]xargin.com/ast/
[Лексический анализатор Golang, анализ исходного кода сканера]blog.CSDN.net/Чжао Руйсянь…
【Объяснение Гопата】flaviocopes.com/go-gopath/
【Понимание GOPATH】woohoo.digital ocean.com/community/he…
【обсуждать】stackoverflow.com/questions/7…
【Перейти к официальному гопату】golang.org/cmd/go/#Многие люди…
[Изучение пакета Go]Tickets.WeChat.QQ.com/Yes/OI в v LX FZ6…
[Официально об организационной структуре проекта Go]golang.org/doc/code Контракты…
【Перейти к модулям】Woohoo Мелвин vivas.com/go-version-…
【Установка, настройка Golang, GOPATH и рабочее пространство Go】woohoo.call ICO people.com/golang-Inst…
[Ссылка на процесс компиляции и компоновки]mikespook.com/2013/11/Перевод-…
[1.5 Компилятор дополнен языком go]Woohoo.info Q.capable/article/201…
[Перейти к серии статей о процессе компиляции]Woohoo. Cto lib.com/topics-3724...
Цао идет бутстрап []GitHub.com/часто123/потяните…
[Процесс запуска Голанга]blog.ice в.com/posts/go/body…
[Изучите процесс запуска программы golang]cbsheng.github.io/posts/explore гол…
[Изучите создание горутин]cbsheng.github.io/posts/ Исследуйте Гор ...