LINUX.ORG.RU

Что происходит в этом коде


2

2
#include <stdio.h>

int main(int argc, char *argv[])
{
        float fl;
        char *cp = (char*) &fl;

        scanf("%f", &fl);

        if (argc > 1) {
                printf("%f\n", fl);
        }

        printf("%f\n", *cp);

        return 0;
}
$echo 3.14 | ./a.out 
0.000000
$echo 3.14 | ./a.out foo
3.140000
3.140000
$

Почему вывод второго printf зависит от вызова первого?


вывод второго или не вывод в зависит от счетчика аргументов командной строки, а именно argc

вот пимер

ivr@nout:/tmp$ gcc test.c 
ivr@nout:/tmp$ ./a.out 
1
ivr@nout:/tmp$ ./a.out her
2
ivr@nout:/tmp$ cat test.c 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
	printf("%d\n", argc);
	return 0;
}
ivr@nout:/tmp$ 
IvanR ★★★
()
Последнее исправление: IvanR (всего исправлений: 1)
pinkbyte@phantom ~/dev $ gcc -pedantic -ansi -Wall -Werror 1.c 
1.c: В функции «main»:
1.c:14:9: ошибка: format «%f» expects argument of type «double», but argument 2 has type «int» [-Werror=format]
cc1: all warnings being treated as errors

nuff said

Pinkbyte ★★★★★
()
Ответ на: комментарий от anonymous

Вангую, нет. scanf согласно манам должен считывать именно float, а больше в память ничего не пишет.

suuaq
() автор топика

Фокус:

#include <stdio.h>

int main(int argc, char *argv[])
{
        float fl;
        char *cp = (char*) &fl;

        scanf("%f", &fl);

        if (argc > 1) {
                printf("%f\n", fl);
        }

        printf("%f\n");

        return 0;
}

Поведение такое же.

NeXTSTEP ★★
()
Ответ на: комментарий от suuaq

У Вас во втором printf в функцию передается тип char, и возникает неопределенное проведение. Попробуйте перед разыменованием привести указатель к float*

urquan
()
Ответ на: комментарий от suuaq

Могу предположить, что printf читает стек, в котором как раз f1

urquan
()
Ответ на: комментарий от NeXTSTEP

Ещё один:

#include <stdio.h>

void foo(void *a, char *b)
{
	printf("Hello, %s!\n");
}

int main(int argc, char *argv[])
{
	foo(0, "world");
        return 0;
}

$ ./a.out
Hello, world!

А суть одна: на стеке остаются старые параметры.

NeXTSTEP ★★
()

Патамучто callabi. float передаётся через совершенно другие регистры.

http://en.wikipedia.org/wiki/X86_calling_conventions -> System V AMD64 ABI

int main(int argc, char *argv[]) {
        float fl;
        char *cp = (char*) &fl;

        scanf("%f", &fl);

        if (argc > 1) {
                printf("%f\n", fl);//тут вызвает принтф, а float/double-аргумент записывается в sse-регистр xmm0+. "%f" - читает xmm0, и кладёт на всё остальное. 
        }

        printf("%f\n", *cp);//Для обычных аргументов юзаются "обычные" регистры - (r)di+. Т.е. формат у тебя (r)di, а *cp в (r)si, но формат "%f" и поэтому printf() читает не (r)si, а xmm0, в котором до этого у тебя было записанна аргумент к предидущему printf()"у.

        return 0;
}
cmpl	$1, %ebx
	jle	.L2
	movl	$.LC1, %edi
	movl	$1, %eax
	vxorpd	%xmm0, %xmm0, %xmm0
	vcvtss2sd	12(%rsp), %xmm0, %xmm0//тут 3.14
	call	printf// тут читается xmm0, ибо формат %f
.L2:
	movsbl	12(%rsp), %esi//тут вот записывается во 2-й аргмент твой разименованный чар.
	movl	$.LC1, %edi
	xorl	%eax, %eax
	call	printf// а принтф читает xmm0, который до сих пор хранит 3.14

Если ты добавишь

	vxorpd	%xmm0, %xmm0, %xmm0
до вызова 2-го притф"а, то у тебя будет 0..

anonymous
()

Именно поэтому по стандарту абсалютно безапасно юзать на amd64:

  printf("%lu\n", uint8_t);
  printf("%lu\n", uint16_t);
  printf("%lu\n", uint32_t);
  printf("%lu\n", uint64_t);

Не заморачиваясь.

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

anonymous
()
Ответ на: комментарий от anonymous

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

anonymous
()
Ответ на: комментарий от emulek

Емулек-емулек, почему ты такйо питушок? Пацан вон хочет что-то понимать, не всем же быть какими нулёвыми обсосками как ты, и на любую непонятную вещь говорить «говнокод это».

anonymous
()
Ответ на: комментарий от anonymous

Вау, Царь показал дамп дизасма. Значит он немного Царь таки... я ошибался.

Twissel ★★★★★
()
Ответ на: комментарий от anonymous

и на любую непонятную вещь говорить «говнокод это».

тут как раз всё понятно. Ты же сам выше всё объяснил. Кроме одного: нахрена так делать?

emulek
()
Ответ на: комментарий от suuaq

Я знаю про ошибку, интересует, что именно происходит.

Благородному дону подсказать как гуглить undefined behaviour? :-)

Pinkbyte ★★★★★
()
Ответ на: комментарий от Pinkbyte

Меня интересует, что происходит на разных платформах во время этого вашего undefined behaviour. Мсьё таки умеет это гуглить?

suuaq
() автор топика
Ответ на: комментарий от suuaq

эммм.. а что не ясного в словосочетании undefined behaviour?

Хинт: происходить может всё что угодно, вплоть до высадки марсиан. Сегфолт, выдача мусора - вот примерный список возможных вариантов.

Pinkbyte ★★★★★
()
Ответ на: комментарий от Pinkbyte

эммм.. а что не ясного в словосочетании undefined behaviour?
Хинт: происходить может всё что угодно, вплоть до высадки марсиан. Сегфолт, выдача мусора - вот примерный список возможных вариантов.

Мне интересно, что конкретно происходит и как оно внутри работает. Мсьё настолько завяз в стандартах и спецификациях, что ему трудно это осознать?

suuaq
() автор топика
Ответ на: комментарий от Pinkbyte

Человеку хочется залезть в самые дебри программирования и узнать что-то новое. Об использовании такого кода в продакшене речи не идёт.

true_admin ★★★★★
()
Ответ на: комментарий от suuaq

Сигнатура вызовов одинаковая. Первый раз в стек кладется флот задом наперед, ты же на интеле, да? Второй раз кладется его младщий байт, а три уже там, т.к. стек по некоторым причинам не успел затереться. Второй принтф ожидает флот и читает его. Если первый раз не выводить, то в стеке чудом три нуля, а младший байт флота тоже ноль, так уж биты легли в 3.14.

В целом многофакторный уб и фейспалм, конечно же.

ps: А ну да, забыл, что флот на другом стеке передаваться может, my bad.

arturpub ★★
()
Последнее исправление: arturpub (всего исправлений: 1)
Ответ на: комментарий от true_admin

Ничего, хоть мильён. Яж там давал линк на википедию - через регистры передаются до 6-ти аргументов. У притф"а - это 5, акромя формата.

После 6-ти юзается стек, но все передачи через стек выравниваются.

int main(void) {
  uint8_t a = 1, b = 2, c = 3, d = 4, e = 5, f = 6, g = 7, h = 8, i = 9, j = 10;
  fprintf(stderr, "%lu, %lu, %lu, %lu, %lu, %lu, %lu, %lu, %lu, %lu\n", a, b, c, d, e, f, g, h, i, j);
}
main:
.LFB2511:
	.cfi_startproc
	subq	$8, %rsp
	.cfi_def_cfa_offset 16
	movq	stderr(%rip), %rdi
	movl	$4, %r9d
	movl	$3, %r8d
	pushq	$10//вот оно
	.cfi_def_cfa_offset 24
	movl	$2, %ecx
	movl	$1, %edx
	xorl	%eax, %eax
	pushq	$9
	.cfi_def_cfa_offset 32
	movl	$.LC0, %esi
	pushq	$8
	.cfi_def_cfa_offset 40
	pushq	$7
	.cfi_def_cfa_offset 48
	pushq	$6
	.cfi_def_cfa_offset 56
	pushq	$5
	.cfi_def_cfa_offset 64
	call	fprintf
	xorl	%eax, %eax
	addq	$56, %rsp
	.cfi_def_cfa_offset 8
	ret

даже если мы изменим типы:

int main(void) {
  uint64_t a = 1, b = 2, c = 3, d = 4, e = 5, f = 6, g = 7, h = 8, i = 9, j = 10;
  fprintf(stderr, "%lu, %lu, %lu, %lu, %lu, %lu, %lu, %lu, %lu, %lu\n", a, b, c, d, e, f, g, h, i, j);
}

Код не поменяется.

main:
.LFB2511:
	.cfi_startproc
	subq	$8, %rsp
	.cfi_def_cfa_offset 16
	movq	stderr(%rip), %rdi
	movl	$4, %r9d//гцц даже клал на типы - он кладёт константы в l/q взависимости от длинны константы - это позволяет матчасть.
	movl	$3, %r8d
	pushq	$10
	.cfi_def_cfa_offset 24
	movl	$2, %ecx
	movl	$1, %edx
	xorl	%eax, %eax
	pushq	$9
	.cfi_def_cfa_offset 32
	movl	$.LC0, %esi
	pushq	$8
	.cfi_def_cfa_offset 40
	pushq	$7
	.cfi_def_cfa_offset 48
	pushq	$6
	.cfi_def_cfa_offset 56
	pushq	$5
	.cfi_def_cfa_offset 64
	call	fprintf
	xorl	%eax, %eax
	addq	$56, %rsp
	.cfi_def_cfa_offset 8
	ret

Да и с чего у тебя вообще возник такой вопрос? Как по твоему сильнобольше одного влияют на что-то?

anonymous
()
Ответ на: комментарий от anonymous

с чего у тебя вообще возник такой вопрос?

Просто было интересно что будет когда регистры кончатся. Я в этом не сильно шарю.

true_admin ★★★★★
()
Ответ на: комментарий от suuaq

Так бы и сказал сразу, а то я не понял. Если интерес чисто академический - тогда ок.

Pinkbyte ★★★★★
()
Ответ на: комментарий от arturpub

Т.е. ты о5 пришел кукарекать там, где ты нихрена не понимаешь?

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

Перед тем как нести такую ересь - тыб почитал про стек, call abi. Ну да, и покаким таким причинам он «не успел затерется», ты конечно же не знаешь?

Ах да, с аргументами через стек это работать не будет. Соебри чтоли -m32.

так уж биты легли в 3.14.

Тыб перед тем как нести такую херню попробовал бы чтоли затереть первы/последний байт у флоата.

И да, это работает с любым числом. И о5 обосрался.

Там почитай про выравнивание стека.

В целом многофакторный уб и фейспалм, конечно же.

Это не уб.

ps: А ну да, забыл, что флот на другом стеке передаваться может, my bad.

В другом стеке. Какой жопой он может? Чего это ты там мог забыть? Ага, в сишке и на х86 2стека.

Ты почитал мой коммент и решил почитать про callabi, упоминания в англ вики про 2стека я не нашел, а вот какой-то намёк в русской у cdecl"а нашел.

кроме чисел с плавающей точкой - они будут в псевдостеке x87 (в регистре ST0).

Наверное ты это и прочитал и начал нести такую хрень про 2стека, но спешлфорю поясню - тут говорится про return.

А как же то, о чем ты кукарекал выше? Т.е. такое поведение вызванно 2-мя стеками? А ты написал ту ересь лишь по причине того, что ты забыл про 2стека? Гениально.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.