Как вставить эквивалент NULL в C в стек в сборке? - PullRequest
0 голосов
/ 05 июня 2019

Я пишу пузырьковую сортировку для сортировки строк на ассемблере и использую strtok () для токенизации строки. Однако после первого вызова strtok (str, "") мне нужно передать NULL в качестве параметра, то есть strtok (NULL, "")

Я пробовал NULL = 0 в сегменте .bss, но это ничего не делает.

[SECTION .data]

[SECTION .bss]

string resb 64
NULL equ 0

[SECTION .text]

extern fscanf
extern stdin
extern strtok

global main

main:

    push ebp        ; Set up stack frame for debugger
    mov ebp,esp
    push ebx        ; Program must preserve ebp, ebx, esi, & edi
    push esi
    push edi

    push cadena
    push frmt
    push dword [stdin]      ;Read string from stdin
    call fscanf
    add esp,12              ;clean stack

    push delim
    push string             ;this works
    call strtok
    add esp,8               ;clean stack

    ;after this step, the return value in eax points to the first word 

    push string             ;this does not
    push NULL
    call strtok
    add esp,8               ;clean stack

    ;after this step, eax points to 0x0

    pop edi         ; Restore saved registers
    pop esi
    pop ebx
    mov esp,ebp     ; Destroy stack frame before returning
    pop ebp
    ret         ;return control to linux

Я читал, что в "большинстве реализаций" NULL указывает на 0, что бы это ни значило. Почему двусмысленность? Что эквивалентно NULL в наборе команд x86?

Ответы [ 3 ]

5 голосов
/ 05 июня 2019
 push NULL 
 push string 
 call strtok

это звонит strtok(string, NULL).Вы хотите strtok(NULL, " "), при условии, что delim содержит " ":

 push delim
 push NULL
 call strtok

Параметры поступают в стек в обратном (справа налево) порядке в соглашении о вызовах cdecl.


Что касается другой части вашего вопроса (NULL всегда равен нулю), см .: Всегда ли NULL равен нулю в C?

3 голосов
/ 06 июня 2019

Я читал, что в "большинстве реализаций" NULL указывает на 0, что бы это ни значило.

Нет, это равно 0;это не указатель на ничего.

В источнике C (void*)0 всегда равен NULL, но реализациям разрешено внутренне использовать другой ненулевой битовый шаблон для представления объектаint *p = NULL;.Реализации, которые выбирают ненулевой битовый шаблон, должны преобразовываться во время компиляции.(И перевод only работает во время компиляции для целочисленных константных выражений времени компиляции со значением ноль, которые появляются в контексте указателя, а не для memset или чего-либо еще.) FAQ по C ++ имеет целоераздел по указателям NULL .(В данном случае это также относится к C.)

(В C допустимо обращаться к битовой структуре объекта с memcpy в целое число или с псевдонимом (char*) на него, так что это возможночтобы обнаружить это в правильно сформированной программе, свободной от неопределенного поведения. Или, конечно, посмотрев содержимое asm или памяти с помощью отладчика! На практике вы можете легко проверить, что правильный asm для NULL, скомпилировав int*foo(){return NULL;})

См. Также Почему нулевой адрес используется для нулевого указателя? для дополнительной информации.

Почему существует неопределенность?Что эквивалентно NULL в наборе команд x86?

Во всех соглашениях / ABI о вызовах x86 битовый шаблон asm для указателей NULL является целым числом 0 .

Итак, push 0 или xor edi,edi (RDI = 0) всегда то, что вам нужно для x86 / x86-64. (Современные соглашения о вызовах, включая все соглашения x86-64, передают аргументы в регистрах.) Ответ

@ J ... показывает, как выдвигать аргументы в порядке справа налево для соглашения о вызовах, которое вы используете , что приводит к первому (крайнему левому)) arg по самому низкому адресу.

На самом деле вы можете хранить их в стеке так, как вам нравится (например, с mov), до тех пор, пока они не окажутся в нужном месте при запуске call.


Стандарт C позволяет ему быть другим, потому что реализации C на некотором оборудовании могут захотеть использовать что-то другое, например, специальный битовый шаблон, который всегда дает сбой при разыменовании, независимо от контекста.Или, если 0 было действительным значением адреса в реальных программах, лучше, если p==NULL всегда ложно для действительных указателей.Или любая другая непонятная аппаратная причина.

Так что да могло бы быть некоторыми реализациями C для x86, где (void*)0 в источнике C превращается в ненулевое целое число в asm,Но на практике их нет.(И большинство программистов рады, что memset(array_of_pointers, 0, size) фактически устанавливает их в NULL, что основывается на том, что битовый шаблон 0, потому что некоторые программы делают это предположение, не думая о том факте, что он не гарантированно будет переносимым).

Это не сделано на x86 ни в одном из стандартных C ABI.(ABI - это набор вариантов реализации, которым следуют все компиляторы, чтобы их код мог вызывать друг друга, например, согласовывать структуру структуры, соглашения о вызовах и что означает p == NULL.)

Я не знаюлюбые современные реализации C, которые используют ненулевые NULL на других 32- или 64-битных процессорах;виртуальная память позволяет легко избежать адреса 0.

http://c -faq.com / null / machexamp.html имеет несколько исторических примеров:

В серии Prime 50 используется сегмент 07777, смещение 0 для нулевого указателя, по крайней мере, для PL / I.Более поздние модели использовали сегмент 0 со смещением 0 для нулевых указателей в C, что требовало новых инструкций, таких как TCNP (Test C Null Pointer), очевидно, в качестве сопроводительной ссылки для [сноски] всего существующего плохо написанного кода C, который сделал неправильнымпредположения.Старые машины Prime с адресацией по словам также были известны тем, что требовали указателей большего размера (char *), чем указатели слова (int *).

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

https://www.quora.com/On-which-actual-architectures-is-Cs-null-pointer-not-a-binary-zero-all-bits-zero сообщает о нахождении ненулевого NULL на 286 Xenix, я думаю, используя сегментированные указатели.


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

например. Linux по умолчанию резервирует 64 кБ адресного пространства. Это помогает, независимо от того, пришло ли оно из NULL-указателя в источнике или какая-то другая ошибка обнуляет указатель целочисленными нулями. 64 КБ вместо страниц с низким 4 КБ индексирует указатель в виде массива, например p[i] с малыми и средними i значениями.

Забавный факт: Windows 95 отображает самые младшие страницы виртуального адресного пространства в пространстве пользователя на первые 64 КБ физической памяти, чтобы обойти ступенчатую ошибку 386 B1. Но, к счастью, он смог все настроить, поэтому доступ из нормального 32-битного процесса дал сбой. Тем не менее, 16-битный код, работающий в режиме DOS Compat, может очень легко уничтожить всю машину.

См. https://devblogs.microsoft.com/oldnewthing/20141003-00/?p=43923 и https://news.ycombinator.com/item?id=13263976

1 голос
/ 06 июня 2019

Вы на самом деле задаете два вопроса:

Вопрос 1

Я прочитал это ... NULL указывает на 0, что бы это ни значило.

Это означает, что почти все компиляторы C определяют NULL как (void *)0.

Это означает, что указатель NULL является указателем на ячейку памяти с нулевым адресом.

Я читал это в "большинстве реализаций" ...

«Большинство» означают, что до введения ISO C и ANSI C в конце 1980-х , были компиляторы C, которые определяли NULL по-другому.

Может быть, еще немного нестандартных компиляторов C, которые не распознают адрес 0 как NULL.

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

Вопрос 2

Как вставить эквивалент NULL в C в стек в сборке?

Указатель - это адрес.

(в отличие от некоторых других процессоров), процессоры x86 не различают целые числа и адреса:

Вы нажимаете указатель NULL, нажимая целочисленное значение 0.

NULL equ 0

push NULL

К сожалению, вы не написали, какой ассемблер вы используете. (Другие пользователи предполагают, что это NASM.)

В этом контексте инструкция push NULL может интерпретироваться разными ассемблерами двумя способами:

  • Некоторые ассемблеры интерпретируют это как: « Нажмите значение 0 ».

    Это было бы правильно.

  • Другие ассемблеры интерпретируют это как: " Считайте память в ячейке памяти 0 и нажмите это значение "

    Это будет равно someFunction(*(int *)NULL) в C и, следовательно, приведет к исключению (NULL доступ к указателю).

...