Программа прекращает работу при соединении с символом в союзе - PullRequest
0 голосов
/ 01 января 2019

Я пытаюсь strcpy объединить с размером 8, следующим образом:

#include <stdio.h>
#include <string.h>


typedef union {
  double num;
  char chr;
} doublechar;

int main (int argc, char *argv[])
{
  doublechar test;
  strcpy(&test, "test");
  printf("%s\n", &test);

  return 0;
}

Это отлично работает.Однако, когда я пытаюсь скопировать по адресу объединения как символ с strcpy или strncpy, программа вылетает с сообщением abort:

strcpy(&test.chr, "test"); // this does not work
strncpy(&test.chr, "test", 3); // this does not work
strcpy(&test.num, "test"); // this works
memcpy(&test.chr, "test", 3); // this works

Во всех четырех случаяхадрес памяти одинаков, так почему некоторые из них терпят неудачу?strcpy и strncpy также не работают с объединением, выделенным для кучи.Кроме того, это, кажется, работает нормально, хотя это не должно:

char *p = &test.chr;
strcpy(p, "test"); // this works

Может кто-нибудь объяснить это?

РЕДАКТИРОВАТЬ: Очевидно, компилятор выдает кучу предупреждений, когда эта программа компилируется, но все они имеют отношение к спецификаторам формата printf.Вот версия программы, которая аккуратно компилируется:

#include <stdio.h>
#include <string.h>


typedef union {
  double num;
  char chr;
} doublechar;

int main (int argc, char *argv[])
{
  doublechar test;
  strcpy(&test.chr, "test");
  printf("%s\n", &test.chr);

  return 0;
}

Я использую следующий компилятор:

Apple LLVM version 10.0.0 (clang-1000.11.45.5)
Target: x86_64-apple-darwin18.2.0
Thread model: posix

Это то, что я вижу при запуске программы:

[1]    74379 abort      a.out

Ответы [ 2 ]

0 голосов
/ 01 января 2019

Причина проста.Вы определили test как doublechar, и поэтому test.chr - это один символ .Когда вы берете указатель на него, он ведет себя для целей индексации , как если бы он был указателем на первый элемент массива длиной 1 .

И вот,

strcpy(&test.chr, "test");

вы пытаетесь скопировать массив длины 5 по массиву длины 1 , и поведение не определено.Не имеет значения, является ли это тот же адрес, что и &test.num - потому что это не единственное, что имеет значение;также имеет значение тип адресуемого элемента, расположение элемента в (возможном) массиве, к которому он принадлежит, и происхождение указателя.

В прошлом это могло быть «не проблемой», посколькунеопределенное поведение означает, что реализация, которая переполняет массив длиной 1 с еще 4 символами, была бы правильной.Теперь компиляторы и реализации C реализуют проверку диапазона во встроенных функциях, и strcpy может защитить вас от записи за пределы известного массива длины 1 и прервать программу до того, как произойдет худшее поведение.Что тоже разрешено стандартом.

Определение неопределенного поведения: 3.4.3p1

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

  2. ПРИМЕЧАНИЕ Возможное неопределенное поведение варьируется от полного игнорирования ситуации с непредсказуемымрезультаты, ведущие себя во время трансляции или выполнения программы задокументированным образом, характерным для среды (с выдачей диагностического сообщения или без него), к прекращению трансляции или выполнения (с выдачей диагностического сообщения).


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

#include <stdio.h>
#include <string.h>


typedef union {
    double num;
    char chrs[sizeof (double)];
} doublechar;

int main (int argc, char *argv[])
{
  doublechar test;
  strcpy(test.chrs, "test");
  printf("%s\n", test.chrs);

  return 0;
}

Для записи, GCC Ubuntu 7.3.0-27ubuntu1 ~ 18.04 ведет себя несколько лучше с вашим последним отрывком - он выдает правильную диагностику :

% gcc union.c -O3
In file included from /usr/include/string.h:494:0,
                 from union.c:2:
In function ‘strcpy’,
    inlined from ‘main’ at union.c:13:3:
/usr/include/x86_64-linux-gnu/bits/string_fortified.h:90:10: warning: 
   ‘__builtin___memcpy_chk’ writing 5 bytes into a region of size 1 overflows the 
   destination [-Wstringop-overflow=]
   return __builtin___strcpy_chk (__dest, __src, __bos (__dest));
          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

% ./a.out        
*** buffer overflow detected ***: ./a.out terminated
zsh: abort (core dumped)  ./a.out

Здесь просто использование переключателей по умолчанию недостаточно;скомпилированный без оптимизации выдает test.

0 голосов
/ 01 января 2019

Это

strcpy(&test, "test");

неверно, компилятор мог бы предупредить вас, как показано ниже, если вы скомпилировали свой код с флагом, например -Wall -Wstrict-prototypes -Wpedantic -Werror.Никогда не игнорируйте предупреждение компилятора.

ошибка: передача аргумента 1 'strcpy' из несовместимого типа указателя [-Werror] strcpy (& test, "test");^

, поскольку &test относится к типу doublechar*, а "test" относится к типу char*, а копирование char* в doublechar* приводит к приведенному выше сообщению об ошибке.

Также здесь

typedef union {
  double num; /* 8 byte gets allocated for whole union as this member needs the highest memory */
  char chr;
} doublechar;

doublechar является объединением, то есть здесь все члены разделяют общий mmory, который составляет 8 байт в 32-bit системе

 --------------------------------------------------
 |                         |                        |
  --------------------------------------------------
 MSB                                           <-- LSB
                                                   num
                                                   chr <-- both num and chr access memory from beginning

Также это

strcpy(&test.chr, "test"); // this does not work
printf("%s\n", &test); /* format specifier is wrong */

вызывает неопределенное поведение , поскольку test.chr имеет тип char, копирование более чем 1 char не рекомендуется, так как это может перезаписать содержимое следующего элемента, но будьте осторожныпри этом.

Также спецификатор формата printf неверен, %s ожидает аргумент типа char* и &test не char*.Вы хотите, как показано ниже

strcpy(&test.chr, "t"); /* test.chr is of char type, */
printf("%c\n", test.chr); /* use %c as chr is of char type*/
printf("%p\n",(void*)&test); /* use %p if you want to print address */

Также здесь

strcpy (& test.num, "test");// это работает

Нет, это не работает , поскольку test.num имеет тип double типа, а не char*, ваш компилятор мог предупредить вас как

примечание: ожидается 'char * restrict ', но аргумент имеет тип 'double *'

, вы можете использовать memcpy() вышеслучай.

...