LINUX.ORG.RU

А как на C решаются проблемы одной переменной на несколько типов?

 


0

3

Возник вопрос. Можно ли в C использовать переменную произвольного типа в зависимости от контекста? Пример кода.

void test(int type) {

 if (type==1) {
  char *data = "Hello world";
 }
 if (type==2) {
  int data = 123;
 }
// ...
}

Экспериментировал с void. Работает лишь наполовину

void test(int type) {

 void *data;
 if (type==1) {
  char *data = "Hello world";
  printf("Data: %s\n",data); // Тут data - правильные
 }
 if (type==2) {
  int data = 123;
 }
// ...
 printf("Data: %s\n",data); // А вот тут data - поломанные
}

Первый printf выводит как положено, Hello world.

А вот второй printf вне условия, выводит �ÐUH��H�� H�}�H�

Благодарю.

★★★★★
Ответ на: комментарий от hibou
iskander in ~/Programs λ gcc num.c -S num.s
iskander in ~/Programs λ cat num.s
        .file   "num.c"
        .text
        .local  a
        .comm   a,4,4
        .local  b
        .comm   b,4,4
        .section        .rodata
        .align 8
.LC0:
        .base64 "aXNrYW5kZXI5OTA4INGD0YLQstC10YDQttC00LDQtdGCINGH0YLQviDQsNC00YDQtdGB0LAg0L3QtSDRgNCw0LLQvdGLOiAlcCAlcAoA"
        .text
        .globl  main
        .type   main, @function
main:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        subq    $32, %rsp
        leaq    a(%rip), %rax
        movq    %rax, -32(%rbp)
        leaq    4+b(%rip), %rax
        movq    %rax, -24(%rbp)
        movq    -32(%rbp), %rax
        movq    %rax, -16(%rbp)
        movq    -24(%rbp), %rax
        movq    %rax, -8(%rbp)
        movq    -32(%rbp), %rax
        cmpq    -24(%rbp), %rax
        je      .L2
        movq    -24(%rbp), %rdx
        movq    -32(%rbp), %rax
        leaq    .LC0(%rip), %rcx
        movq    %rax, %rsi
        movq    %rcx, %rdi
        movl    $0, %eax
        call    printf@PLT
.L2:
        movl    $0, %eax
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:
        .size   main, .-main
        .ident  "GCC: (GNU) 15.2.1 20250813"
        .section        .note.GNU-stack,"",@progbits
iskander in ~/Programs λ gcc num.s -o num  
iskander in ~/Programs λ ./num
iskander9908 утверждает что адреса не равны: 0x561d8334401c 0x561d83344024

Если я ничего не напутал - то там есть сравнение

iskander9908
()
Последнее исправление: iskander9908 (всего исправлений: 1)

Возник вопрос. Можно ли в C использовать переменную произвольного типа в зависимости от контекста?

У меня такое компилятор отказывается собирать. Идёт двойное объявление data, как указатель типа void* и переменной int

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

ты наверное хотел &a + 1. для отдельных переменных нет гарантий как они физически расположены или чё-то там

И я об этом

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

godbolt.org это очень известный сайт, глупо с твоей стороны обвинять его в подделке результата. Ну давай запущу на своем ПК:

$ gcc -O2 main.c
$ ./a.out 
iskander9908 утверждает что адреса не равны: 0x404038 0x404038
$ gcc --version
gcc (GCC) 11.2.0
Не знаю как измеряется авторитетность godbolt.org, и в чем, но на GitHub у него 17 000 звездочек.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 7)
Ответ на: комментарий от iskander9908

Ты ничего про оптимизацию не писал до этого. Ну и что значит «дело в оптимизации»? Да, я включил оптимизацию, которая обычно и включается при компиляции программ в релизной сборке, иногда и более «агрессивные» флаги. Дистрибутивы обычно с -O2 и собирают программы.

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

Или у тебя указатель число, но только без оптимизаций? Непорядок. Ну или очень странное определение числа, это какой раздел математики?

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

Ты видишь здесь где-нибудь сравнение?

iskander in ~/Programs λ gcc num.c -O2 -S num.s
iskander in ~/Programs λ cat num.s             
	.file	"num.c"
	.text
	.section	.rodata.str1.8,"aMS",@progbits,1
	.align 8
.LC0:
	.base64	"aXNrYW5kZXI5OTA4INGD0YLQstC10YDQttC00LDQtdGCINGH0YLQviDQsNC00YDQtdGB0LAg0L3QtSDRgNCw0LLQvdGLOiAlcCAlcAoA"
	.section	.text.startup,"ax",@progbits
	.p2align 4
	.globl	main
	.type	main, @function
main:
.LFB11:
	.cfi_startproc
	subq	$8, %rsp
	.cfi_def_cfa_offset 16
	leaq	4+b(%rip), %rdx
	leaq	a(%rip), %rsi
	xorl	%eax, %eax
	leaq	.LC0(%rip), %rdi
	call	printf@PLT
	xorl	%eax, %eax
	addq	$8, %rsp
	.cfi_def_cfa_offset 8
	ret
	.cfi_endproc
.LFE11:
	.size	main, .-main
	.local	b
	.comm	b,4,4
	.local	a
	.comm	a,4,4
	.ident	"GCC: (GNU) 15.2.1 20250813"
	.section	.note.GNU-stack,"",@progbits

Оптимизация выкинула сравнение, а ожидаемое поведение программы - это то, о чём я говорил.

Лучше бы учил язык, прежде чем спорить

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

Ты ничего про оптимизацию не писал до этого.

Тебе hibou сказал:

Я запустил локально. С оптимизациями. И посмотрел ассемблерный листинг. Там сравнение указателей вообще выброшено. Компилятор по стандарту имеет право так сделать. И он это сделал.

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

В этом и заключается оптимизация, компилятор устраняет ненужные действия. Впервые узнал? Вот компилятор и решил, числовые значения указателей верны, а сами указатели нет, потому что указатели не числа. И раскрыл ветви в КТ.

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

Давай лучше обучу тебя логике, утверждение «все Х удовлетворяют требованию Y» верно только если нету «Х» который НЕ «удовлетворяет требованию Y». Так вот, твое утверждение ложно, потому что я нашел пример где это не так. Мое утверждение заключалось в том, что указатель это не число, но может удачно совпасть так, что он будет вести себя как будто это число. Это верно, иначе можешь найти опровержение.

Кстати, ты что предлагаешь, компилировать без оптимизаций теперь программы? А то ведь она убирает проверки. Можешь еще «баг» зарепортить в gcc, тебе там и ссылку на стандарт дадут.

Тебе hibou сказал

Про тебя речь, ему я уже ответил.

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

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

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

Я не разбираюсь какие оптимизации использует компилятор, но в книжках чёрным по белому пишут: как всё работает. Советую тебе хоть раз их прочитать.

Не вижу больше смысла больше спорить с человеком, который говорит, что указатель - как и всё не набор битов.

Живи долго и процветай, мой друг!

iskander9908
()
Последнее исправление: iskander9908 (всего исправлений: 5)
Ответ на: комментарий от MOPKOBKA

Простите, извините, что вмешиваюсь, но тут еще одна мысль возникла. 😊

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

Например, в dos, и вообще в 16-ти битном реальном режиме процессора организация памяти была сегментная. Два указателя могут содержать короткий адрес внутри сегментов и совпадать. А сегменты могут быть разные. Поэтому сравнение таких указателей не имеет смысла. Хотя численно они тоже равны.

Я про то, указатели не обязательно не числа. Просто не все операции над ними имеют смысл как над числами.

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

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

Мне бы тоже хотелось узнать. Такие компьютеры не работают в рамках булевой алгебры - это что-то новое. Заслуживает премии Тьюринга.

iskander9908
()
Последнее исправление: iskander9908 (всего исправлений: 2)
Ответ на: комментарий от hibou

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

Как по мне так больше проблем вызывает:

Because of the way the segment address and offset are added, a single linear address can be mapped to up to 2^12 = 4096 distinct segment:offset pairs.
bugfixer ★★★★★
()
Ответ на: комментарий от hibou

Я про то, указатели не обязательно не числа. Просто не все операции над ними имеют смысл как над числами.

На самом деле, это живой пример неопределённого поведения программы(UB). Если бы он написал присвоил pb адрес a - то «Морковка бы не победил», даже с оптимизацией. Оптимизация работает тогда, когда программа написана корректно и нет «нежданчиков».

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

pb = &a; – это неинтересно, это уже явная подсказка компилятору, что адреса одного и того же объекта. Да какая подсказка, явное указание – на третьи сутки индеец Зоркий Глаз увидел, что стены-то нет.

А тут компилятор таки эпично попался. Ну как попался… сделал как договорились большие дяди, умные. И их договоренности высечены в камне – в стандарте. Типа – мы вам говорили.

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

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

А тут компилятор таки эпично попался. Ну как попался… сделал как договорились большие дяди, умные. И их договоренности высечены в камне – в стандарте. Типа – мы вам говорили.

Не думаю, что они рассчитывали его на криворукие программы

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

Вижу вы серьёзный дяденька. Я не настолько хорошо знаю стандарт - буду иметь в виду.

iskander9908
()
Последнее исправление: iskander9908 (всего исправлений: 2)
Ответ на: комментарий от iskander9908

Да ну брось, я тоже учусь методом проб и ошибок. Метод научного тыка. :-)

Морковка всех зарулил. Я полчаса тупил в его пример кода, как баран на новые ворота. Еще и понедельник – день тяжелый. А тут магия вне Хогвартса. Потом вспомнил, что могу читать ассемблер и магия немного растворилась. Потом стал искать правила сравнения указателей и думать почему умные дяди такие умные.

Будем учиться вместе.

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

Вот тут есть статья с объяснением ситуации. Там приведен комментарий комитета на эту тему:

https://habr.com/ru/companies/pvs-studio/articles/418023/

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

Ну ладно, указатель это число, но только если использовать gcc с выключенной оптимизацией, я тебя понял. А любые ситуации где указатель не ведет себя как число, объясняются например погодой, или другим сторонним явлением.

Если указатель это число, почему стандарт запрещает отнимать произвольные значения от указателя? Страница 556. Это что за число такое?

Почему по стандарту запрещенно делать так? Страница 649. И даже есть архитектура где так делать совсем-совсем нельзя, страница 15.

void my_realloc(void *p, size_t size)
{
  void *new_p;

  new_p = realloc(p, size);
  printf("offset = %zu\n", new_p - p); // <--
}

Почему по стандарту запрещенно в конце концов делать так? Страница 95, пункт 5.

int a, b;

int *pa = &a;
int *pb = &b;

pa += (pb - pa);
return pa == pb;

Почему я знаю, можешь не отвечать, это все UB. B все это хорошо показывает что указатель не является числом с диапазоном включающим 0...(V)RAM_SIZE. Он не обладает просто свойствами числа о котором ты говоришь!

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

Кстати, вместо дампов позиционно-независимого кода советую осилить godbolt, очень удобно, и можно установить локально.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 11)
Ответ на: комментарий от hibou

Я про то, указатели не обязательно не числа.

Указатель может быть как на простую переменную любого базового типа, так и на адрес.
Например аргумент с типом & удобен в том случае, если вы хотите изменить значение адреса в функции и получить новое его значение.
Можно и **&
***&, но нужно понимать «зачем?».

И ещё.
Указатели при сравнении могут разниться, но указывать на один и тот же байт в памяти.

Вот СМ-1M, СЬ-2M, … было в разделе выделено 2KB, для косвенных указателей так как позволяло много удобней записывать алгоритмы.
Приблизительно это так: &&&&&& указывают на шесть разных таблиц,

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

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

См. https://learn.microsoft.com/ru-ru/windows/win32/memory/virtual-address-space Виртуальное адресное пространство (управление памятью)
и конечно тексты на которые указывают ссылки.

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

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

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

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

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

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

Вот морковка выше писал, что есть где-то указатели, в которых входит еще что-то кроме адреса. Но пока это только слова. Назовите пожалуйста архитектуры, где оно так.

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

Вот морковка выше писал, что есть где-то указатели, в которых входит еще что-то кроме адреса. Но пока это только слова. Назовите пожалуйста архитектуры, где оно так.

Об этом у Руссиновича подробно говорится (и весьма толково).

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

Вот морковка выше писал, что есть где-то указатели, в которых входит еще что-то кроме адреса. Но пока это только слова. Назовите пожалуйста архитектуры, где оно так.

CHERI

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

__near и __far смешивались, и в нотации Win32 это еще осталось.

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

Я пока вот что нашел:

https://learn.microsoft.com/ru-ru/windows/win32/winprog64/virtual-address-space

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

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

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

Ранее Microsoft писала о том, что в Windows имеется подсистема
обеспечивающая функционирование наиболее популярных программ,
собранных для старых версий Windows.

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

Благодарю, утолили мою жажду знания. И все-таки, не покидает чувство, что комитет просто открестился от сложностей более продуманной реализации указателей на разных платформах. Это же все-равно делает компилятор. Значит процедуры ptr++, ptr– написали, они же тоже залазят внутрь структуры указателя и прибавляют, убавляют там определенное поле. Так что мешало написать корректно сравнение полей указателя? Ну да, так сложнее наверное.

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

наверное потому что это может потребовать дёргать внешний код или вообще ядро

в любом случае как обычно выбирают наихудший вариант - поставить всем подножку из-за пары наркоманских архитектур

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

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

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

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

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

hibou ★★★★★
()