Q:

Различия [] и * [C]

Несмотря на то, что с Си я работаю уже довольно давно, я не обращал внимания на возможные различия между двумя способами создания указателей на область памяти. Пример:
char * _item1;
char _item2[];

Есть ли существенная разница между этими способами?

A:

Да, разница конечно есть.
Код вида
char data[];

не скомпилируется. Потому что компилятору нужно знать размер массива. Эта форма используется, если размер массива компилятор может вычислить. Например в такой форме
char data[] = "test";
Это равносильно:
char data[5] = "test"; // плюс один символ для концевого нулевого
char * data - таким образом объявляется указатель на символ (или строку символов, в зависимости от контекста).
Но дело в том, что в С указатели и массивы очень связаны (если честно, то в С нет массивов - есть только указатели) и всегда важно помнить простую формулу:
a[x] равносильно *(a + x)

Теперь, встретив в коде что то вида
4[a] = 3;

Не будете пугаться, а просто поймете, что это
a[4] = 3;

И для закрепления эффекта вот такой код. Функция boolToStr возвращает строку False для нулевого аргумента и True для всех остальных.
#include <stdio.h>
#include <stdlib.h>

const char * boolToStr(int i) {
   return "False\0True" + ((i&&1)*6);
}

int main() {
   printf("%s\n", boolToStr(0));
   printf("%s\n", boolToStr(1));
   printf("%s\n", boolToStr(2));
   printf("%s\n", boolToStr(3));
   printf("%s\n", boolToStr(4));
   printf("%s\n", boolToStr(-1));
   printf("%s\n", boolToStr(-2));

   return 0;
}

A:

Различий, если честно, довольно много. Массив содержит символы, указатель же лишь указывает на них. В этом базовое отличие, из которого вытекают все остальные. 
Рассмотрим объявление переменных:
char* p = "ЖЖЖ";
char q[] = "ЖЖЖ";

Инициализация:
В нашем объявлении p -- указатель на глобально аллоцированную константную строку "ЖЖЖ\0". Поскольку p указывает на литерал, значения, полученные при разыменовании p доступны только для чтения, и *p = 'В' -- представляет собой undefined behaviour (на многих архитектурах так сразу access violation).
q же указывает на массив данных из 4-ёх элементов 'Ж', 'Ж', 'Ж', '\0', доступный и для записи, известного при компиляции размера, и аллоцированный в текущем scope. Это, в частности, означает, что возвращать значение q за пределы текущей функции нельзя. Физическое размещение данных такое же, как и в строке -- в частности, поэтому массив можно использовать как строку.
Кроме того (это уже замечено в других ответах), для q вы не можете опустить инициализацию, в отличие от объявления p. Если вы указываете размер массива явно, количество элементов в инициализаторе может быть меньше, чем требуется.
Изменяемость:
В отличие от p, q является константой -- компилятор не даст переприсвоить его значение. Значение же p можно свободно менять по ходу программы.
Размер: (подмечено в комментариях)
Оператор sizeof для массива возвращает размер массива -- количество байт в нём. Оператор sizeof для указателя возвращает, разумеется, размер указателя в памяти, который никак не связан с размером строки, лежащей по этому указателю. Заметьте, что strlen не обязательно возвратит вам размер массива:
char oops[] = "Lala\0fa"; // strlen(oops) == 4

Конверсии:
В большинстве контекстов, если ожидается char*, переменная типа char[N] будет автоматически приведена к ожидаемому типу, и её значением будет указатель на первый элемент массива (исключения: sizeof, &). Переменные же типа char* не приводятся к типу char[N].
Исключение: формальные параметры функций. Для них char* и char[] практически одно и то же.
Передача параметров:
В языке C параметры-массивы передаются по ссылке, указатели (да и все другие типы аргументов) -- по значению. Это сделано специально, чтобы указатель на массив и массив вели себя одинаково.
Вот источник информации: Arrays and Pointers/C FAQ.

A:

Различаться они будут типом (К.О.) со всеми вытекающими:

1) адресная арифметика
   int a[3] = {1,2,3};
   int *pa = a;

   cout <<&a[0]<<endl;  //адрес первого элемента массива
   cout <<pa<<endl; //тоже самое
   cout <<&a<<endl; //адрес массива, он же адрес первого элемента

   cout <<&a[0]+1<<endl; //адрес второго элемента
   cout <<pa+1<<endl; //тоже самое
   cout <<&a+1<<endl; //магия тут

но при передаче в функцию массив уже становиться указателем, то есть передать массив в функцию нельзя
void f(int* a)
{
    cout <<&a<<endl;
    cout <<&a+1<<endl;
}

2) генерируемый код

a[0] = 13;
pa[0] = 13;

сгенерируемые инструкции для каждого присваивания могут быть различны
3) расположение данных
{
    ...
    int a[3] = {1,2,3}; //будет расположен на стеке, в текуйщей области видимости
    ...
}

int arr[2] = {4,2};
...
{
    ...
    int* pArr = arr; //просто скопирует адрес
    ...
}

также при динамическом выделении нужно использовать delete [],вместо delete, чтобы компилятор корректно интерпретировал область памяти
struct bar
{
   bar()
   {
      cout <<"bar ctor"<<endl;   
   }

  ~bar()
  {
     cout <<"bar dtor"<<endl;
  }
};

bar* da = new bar[3];

//delete da; //выведет bar dtor только 1 раз
delete [] da; //вызовет нужное количество деструкторов

4) шаблоны
различие в типах можно продемострировать специализацией шаблонов
template<class T>
void foo()
{
  cout <<"other"<<endl; 
}

template<>
void foo<int*>()
{
  cout <<"pointer"<<endl;       
}

template<>
void foo<int[]>()
{
  cout <<"array"<<endl;       
}

foo<int*>();
foo<int[]>();
foo<short>();

Код

