Почему printf печатает переменную, не переданную в качестве аргумента? - PullRequest
0 голосов
/ 25 июня 2019

Я давно не писал C-код, я ржавый.Кто-нибудь знает, почему следующий код выводит "rtyaze" на стандартный вывод?Я ожидал "rty".

#include <stdio.h>

int main (void) {
  char s[] = "aze";
  char ss[][3] = { "rty" };
  printf("%s\n", ss[0]);
}

Ответы [ 6 ]

4 голосов
/ 25 июня 2019

Если ваша строка в первом элементе ss содержит 3 символа, вы удаляете нулевой терминатор.

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

Если вы измените 3 в ss [] [3] на 4, вы должны получить ожидаемое поведение.

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

Объявление массива не оставляет места для завершающего нулевого символа, поэтому в конце "rty" нет нулевого символа. Поскольку формат %s требует в качестве аргумента строки с нулевым символом в конце, вы вызываете неопределенное поведение.

В этом случае память для s оказалась сразу после памяти для ss, поэтому printf() распечатал ее при поиске нулевого терминатора.

Измените вашу декларацию на:

char ss[][4] = { "rty" };
2 голосов
/ 25 июня 2019

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

Вы объявили массив, единственный (первый) элемент которого не содержит строки.

char ss[][3] = { "rty" };

Фактически массив объявлен следующим эквивалентным способом

char ss[][3] = { { 'r', 't', 'y' } };

, то есть конечный ноль строкового литерала, был исключен из списка инициализаторов, поскольку размер внутреннего массива равен только 3.

Для вывода массива вы можете написать

printf("%3.3s\n", ss[0]);

явно указав количество символов, которые вы собираетесь вывести.

Если вы хотите вывести его в виде строки, вы должны увеличить его как

char ss[][4] = { "rty" };

чтобы включить конечный ноль строкового литерала "rty".

В случае исходной программы кажется, что компилятор поместил в стек массивы в следующем порядке ss, а затем s. То есть память, выделенная для массивов, выглядит следующим образом.

{ 'r', 't', 'y', 'a', 'z', 'e', '\0' }
  |___________|  |_________________|
      ss                  s

Обратите внимание, что это объявление

char s[] = "aze";

эквивалентно

char s[] = { 'a', 'z', 'e', '\0' };

, то есть строковый литерал включает в себя завершающий ноль, и, следовательно, массив s будет содержать строку.

Также вы должны знать, что такое объявление

char ss[][3] = { "rty" };

не допускается в C ++. В C ++ вы должны написать по крайней мере, как

char ss[][4] = { "rty" };
2 голосов
/ 25 июня 2019

char ss[][3] = { "rty" }; определяет массив массивов 3 char.Поскольку количество массивов не указано (ничего не находится внутри []), оно определяется путем подсчета инициализаторов.Есть только один инициализатор, строковый литерал "rty".Таким образом, результатом является массив из 1 массива 3 char, который содержит r, t и y.Хотя строковый литерал "rty" неявно содержит нулевой символ, массив определен так, чтобы явно содержать только три символа, поэтому нулевой символ не становится частью массива.

printf("%s\n", ss[0]); передает адреспервый символ от ss[0] до printf.Результирующее поведение не определено, потому что printf должен быть передан первый символ строки , что означает последовательность символов, оканчивающихся нулевым символом, но ss[0] не содержит нулевого символа.

В некоторых случаях, когда вы делаете это, другой объект, определенный как char s[] = "aze";, может следовать за ss в памяти, а printf, пока он пытается напечатать строку, может продолжаться после r,t и y, чтобы напечатать a, z и e, после чего он находит нулевой терминатор.

В других случаях, когда вы делаете это, другой объект, s, может не следовать ss вобъем памяти.Компилятор мог удалить s во время оптимизации, так как он не используется и, следовательно, не нужен в программе.Или компилятор мог бы поместить его в другое место.В таких случаях printf может продолжаться в другой памяти и печатать другие символы, или он может продолжаться в недоступной памяти и вызывать нарушение сегмента или другое завершение программы.

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

В конечном счете, поведение не определяется стандартом C.

2 голосов
/ 25 июня 2019

Строка в C состоит из последовательности символов, оканчивающихся нулевым байтом. Элементы ss не имеют достаточно места для хранения данной строки, которая занимает 4 байта, включая нулевой терминатор. Затем, когда вы пытаетесь напечатать ss[0], вы читаете за концом массива. Это вызывает неопределенное поведение.

Измените размер второго измерения массива на 4, чтобы оставить достаточно места.

0 голосов
/ 25 июня 2019

Я провел этот эксперимент:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    char end[] = "\0";
    char layout[7] = " layout";
    char stack[6] = " stack";
    char the[4] = " the";
    char is[3] = " is";
    char this[4] = "This";

    printf("%s\n", this);
    return 0;
}

Выход MacOS (LLVM)

This is the stack layout

Вывод Linux (gcc)

This stack layout

Работа с GDB в Linux показала, что переменные были объявлены в стеке в другом порядке, чем в коде. В частности

(gdb) print &this[0]
$8 = 0x7fffffffe287 "This stack layout"
(gdb) print &is[0]
$9 = 0x7fffffffe280 " is theThis stack layout"

Причина, по которой ваша программа «печатает переменную, не переданную в качестве аргумента», заключается в том, что ваш «rty» не завершен нулем. Это заставляет printf продолжать печатать символы, пока if не найдет нулевой терминатор.

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

...