Как уже отмечали другие, это, вероятно, неопределенное поведение , но программисты старой школы C знают, как это работает.
Кроме того, потому что я чувствую, как юристы по языку разрабатывают своиСудебные документы и судебные ходатайства о том, что я собираюсь сказать, я собираюсь наложить заклинание undefined behavior discussion
.Он произнес три раза: «1006», когда стучал по моей обуви.И это заставляет языковых адвокатов исчезнуть, поэтому я могу объяснить, почему странные вещи просто случаются, работают без предъявления иска.
Вернуться к моему ответу:
Все, что я обсуждаю ниже, является поведением, специфичным для компилятора.Все мои симуляции выполняются с помощью Visual Studio, скомпилированной как 32-битный код x86.Я подозреваю, что он будет работать одинаково с gcc и g ++ на аналогичной 32-битной архитектуре.
Вот почему ваш код просто работает и некоторые предостережения.
Когда аргументы вызова функции помещаются в стек, они помещаются в обратном порядке.Когда f
вызывается нормально, компилятор генерирует код для помещения аргумента b
в стек перед аргументом a
.Это помогает упростить различные функции аргументов, такие как printf.Поэтому, когда ваша функция f
обращается к a
и b
, она просто обращается к аргументам в верхней части стека.При вызове через g
в стек был добавлен дополнительный аргумент (30), но он был передан первым.20 было нажато следующим, затем 10, которое находится на вершине стека.f
рассматривает только два верхних аргумента в стеке.
IIRC, по крайней мере в классическом ANSI C, символы и шорты, всегда переводятся в int, прежде чем помещаются встек.Вот почему, когда вы вызываете его с g
, литералы 10 и 20 помещаются в стек в виде полноразмерных целых вместо 8-битных.Однако в тот момент, когда вы переопределяете f
для получения 64-битных длин вместо 32-битных, выход вашей программы изменится.
void f(int64_t a, int64_t b) {
cout << a << " " << b << endl;
}
В результате вы получитеваш основной (с моим компилятором)
85899345930 48435561672736798
А если вы преобразуете в гекс:
140000000a effaf00000001e
14
равно 20
, а 0A
равно 10
.И я подозреваю, что 1e
- это ваш 30
, помещенный в стек.Таким образом, аргументы передавались в стек при вызове через g
, но были объединены каким-то специфическим для компилятора способом.( неопределенное поведение снова, но вы можете видеть, что аргументы были выдвинуты).
Когда вы вызываете функцию, обычное поведение состоит в том, что вызывающий код исправит указатель стека после возврата из вызываемой функции.Опять же, это ради переменных функций и других унаследованных причин для сравнения с K & R.
printf
не знает, сколько аргументов вы фактически передали ему, и полагается, что вызывающая сторона исправит стек при возврате.Поэтому, когда вы вызываете через
g
, компилятор сгенерировал код, чтобы поместить 3 целых числа в стек, вызвать функцию, а затем код, чтобы вытолкнуть те же самые значения.В тот момент, когда вы изменяете опцию компилятора, чтобы вызываемый объект очищал стек (ala
__stdcall
в Visual Studio):
void __stdcall f(int32_t a, int32_t b) {
cout << a << " " << b << endl;
}
Теперь вы явно находитесь в неопределенной области поведения.Вызов через g
поместил три аргумента int в стек, но компилятор только сгенерировал код для f
, чтобы вытолкнуть два аргумента int из стека при его возврате.Указатель стека поврежден при возврате.